From c89ea59edac2f399ed5a6ee70c4cc60271d9aab8 Mon Sep 17 00:00:00 2001 From: Omar Aljarrah <50204418+OmarAlJarrah@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:47:09 +0300 Subject: [PATCH 01/15] feat: lodging connectivity sdk based on google api client library (#63) PR: https://github.com/ExpediaGroup/lodging-connectivity-java-sdk/pull/63 --- code/build.gradle | 8 + .../sdk/core/model/TransactionId.kt | 30 +++ .../sdk/core/model/paging/ResponseState.kt | 2 +- .../core/plugin/logging/RequestBodyLogger.kt | 2 +- .../core/plugin/logging/ResponseBodyLogger.kt | 4 +- .../configuration/ClientConfiguration.kt | 1 - .../configuration/EndpointProvider.kt | 2 +- .../apache/util/ApacheHttpTransportUtil.kt | 93 +++++++ ...2CredentialsWithRefreshBuilderExtension.kt | 17 ++ .../strategy/AuthenticationStrategy.kt | 13 + .../BearerAuthenticationHandlerFactory.kt | 149 +++++++++++ .../util/HttpCredentialsAdapterUtil.kt | 83 ++++++ .../sdk/v2/core/client/ApiClient.kt | 43 +++ .../core/client/ApiClientApolloHttpEngine.kt | 84 ++++++ .../sdk/v2/core/client/ApiClientBuilder.kt | 48 ++++ .../sdk/v2/core/client/SdkClient.kt | 102 ++++++++ .../sdk/v2/core/client/util/ApiClientUtil.kt | 40 +++ .../v2/core/client/util/GsonFactoryUtil.kt | 18 ++ .../configuration/DefaultClientBuilder.kt | 75 ++++++ .../ExpediaGroupDefaultClientConfiguration.kt | 45 ++++ .../configuration/FullClientConfiguration.kt | 137 ++++++++++ .../sdk/v2/core/configuration/SdkMetadata.kt | 27 ++ .../sdk/v2/core/constant/Authentication.kt | 5 + .../sdk/v2/core/constant/Constant.kt | 35 +++ .../sdk/v2/core/constant/ExceptionMessage.kt | 24 ++ .../sdk/v2/core/constant/HeaderKey.kt | 26 ++ .../sdk/v2/core/constant/HeaderValue.kt | 20 ++ .../expediagroup/sdk/v2/core/constant/Key.kt | 26 ++ .../sdk/v2/core/constant/LogMaskingFields.kt | 36 +++ .../sdk/v2/core/constant/LoggerName.kt | 21 ++ .../sdk/v2/core/constant/LoggingMessage.kt | 28 ++ .../sdk/v2/core/http/BlobTypeDetector.kt | 21 ++ .../sdk/v2/core/jackson/Config.kt | 7 + .../expediagroup/sdk/v2/core/jackson/Util.kt | 25 ++ .../sdk/v2/core/logging/ExpediaGroupLogger.kt | 72 +++++ .../core/logging/ExpediaGroupLoggerFactory.kt | 30 +++ .../v2/core/logging/LogMessageConstants.kt | 23 ++ .../sdk/v2/core/logging/LogMessageTag.kt | 16 ++ .../v2/core/logging/LoggableContentTypes.kt | 21 ++ .../mask/ExpediaGroupJsonFieldFilter.kt | 19 ++ .../ExpediaGroupJsonFieldPatternBuilder.kt | 17 ++ .../sdk/v2/core/logging/mask/MaskLogs.kt | 100 +++++++ .../expediagroup/sdk/v2/core/model/Headers.kt | 25 ++ .../expediagroup/sdk/v2/core/model/Nothing.kt | 21 ++ .../sdk/v2/core/model/Operation.kt | 33 +++ .../sdk/v2/core/model/OperationParams.kt | 22 ++ .../sdk/v2/core/model/Properties.kt | 36 +++ .../sdk/v2/core/model/Response.kt | 66 +++++ .../sdk/v2/core/model/TransactionId.kt | 30 +++ .../sdk/v2/core/model/UserAgent.kt | 20 ++ .../model/exception/ExpediaGroupException.kt | 27 ++ .../client/ExpediaGroupClientException.kt | 29 +++ .../ExpediaGroupConfigurationException.kt | 27 ++ .../ExpediaGroupInvalidFieldNameException.kt | 30 +++ .../service/ExpediaGroupAuthException.kt | 43 +++ ...xpediaGroupServiceDefaultErrorException.kt | 21 ++ .../service/ExpediaGroupServiceException.kt | 30 +++ .../sdk/v2/core/request/Request.kt | 246 ++++++++++++++++++ .../ApiClientRequestInitializer.kt | 73 ++++++ .../initializer/InitializerFunctions.kt | 20 ++ .../initializer/SdkRequestInitializer.kt | 28 ++ .../HttpRequestLoggingInterceptor.kt | 125 +++++++++ .../HttpResponseLoggingInterceptor.kt | 126 +++++++++ .../expediagroup/sdk/v2/core/trait/Trait.kt | 7 + .../AuthenticationHandlerTrait.kt | 19 ++ .../authentication/RefreshAccessTokenTrait.kt | 15 ++ .../sdk/v2/core/trait/common/BuilderTrait.kt | 12 + .../core/trait/common/ConfigurationTrait.kt | 15 ++ .../sdk/v2/core/trait/common/IdTrait.kt | 16 ++ .../trait/configuration/AuthEndpointTrait.kt | 12 + .../AuthenticationStrategyTrait.kt | 17 ++ .../configuration/ClientConfiguration.kt | 16 ++ .../configuration/ClientConfigurationTrait.kt | 10 + .../configuration/ConnectionTimeoutTrait.kt | 13 + .../core/trait/configuration/EndpointTrait.kt | 11 + .../configuration/FullConfigurationTrait.kt | 33 +++ .../v2/core/trait/configuration/KeyTrait.kt | 15 ++ .../MaskedLoggingBodyFieldsTrait.kt | 14 + .../MaskedLoggingHeadersTrait.kt | 14 + .../MaxConnectionsPerRouteTrait.kt | 16 ++ .../configuration/MaxConnectionsTotalTrait.kt | 15 ++ .../configuration/RequestTimeoutTrait.kt | 15 ++ .../core/trait/configuration/SecretTrait.kt | 16 ++ .../trait/configuration/SocketTimeoutTrait.kt | 15 ++ .../ToClientConfigurationTrait.kt | 13 + .../configuration/ClientConfiguration.kt | 195 ++++++++++++++ .../configuration/ClientEnvironment.kt | 38 +++ .../configuration/EndpointProvider.kt | 103 ++++++++ .../client/FileManagementClient.kt | 133 ++++++++++ .../models/FileUploadRequest.kt | 12 + .../filemanagement/models/GenericError.kt | 93 +++++++ .../models/Upload201Response.kt | 72 +++++ .../models/exception/ApiException.kt | 80 ++++++ .../exception/PropertyConstraintViolation.kt | 31 +++ .../PropertyConstraintViolationException.kt | 31 +++ .../operations/FileDownloadOperation.kt | 32 +++ .../operations/FileUploadOperation.kt | 92 +++++++ .../PropertyConstraintsValidator.kt | 52 ++++ .../graphql/BaseGraphQLClient.kt | 115 ++++++++ .../graphql/GraphQLExecutor.kt | 26 ++ .../graphql/adapter/DateTimeAdapter.kt | 44 ++++ .../graphql/adapter/URLAdapter.kt | 51 ++++ .../graphql/adapter/ZoneDateTimeAdapter.kt | 43 +++ .../graphql/payment/PaymentClient.kt | 51 ++++ .../sandbox/SandboxDataManagementClient.kt | 53 ++++ .../graphql/supply/SupplyClient.kt | 55 ++++ code/src/main/resources/test.txt | 1 + code/tasks-gradle/generateProperties.gradle | 24 ++ .../FileManagementExample.java | 26 ++ .../v2/FileManagementExample.java | 26 ++ ...ndboxDataManagementClientUsageExample.java | 118 +++++++++ .../main/resources/simplelogger.properties | 1 + 112 files changed, 4594 insertions(+), 6 deletions(-) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt create mode 100644 code/src/main/resources/test.txt create mode 100644 code/tasks-gradle/generateProperties.gradle create mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java create mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java create mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java create mode 100644 examples/src/main/resources/simplelogger.properties diff --git a/code/build.gradle b/code/build.gradle index 89816033..264fc20f 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -33,7 +33,14 @@ dependencies { implementation 'com.apollographql.adapters:apollo-adapters-core:0.0.4' implementation 'com.apollographql.ktor:apollo-engine-ktor:0.0.2' + /* Apollo Java */ + implementation("com.apollographql.java:client:0.0.2") + /* EG SDK Core */ + implementation("org.apache.tika:tika-core:2.9.2") + implementation("com.google.api-client:google-api-client:2.7.0") + implementation("com.ebay.ejmask:ejmask-api:1.0.3") + implementation("com.ebay.ejmask:ejmask-extensions:1.0.3") implementation 'org.slf4j:slf4j-simple:2.0.13' implementation 'io.ktor:ktor-client-core:2.3.10' implementation 'io.ktor:ktor-client-auth-jvm:2.3.10' @@ -50,6 +57,7 @@ dependencies { implementation 'jakarta.validation:jakarta.validation-api:2.0.2' } +apply from: "tasks-gradle/generateProperties.gradle" apply from: "tasks-gradle/apollo.gradle" apply from: "tasks-gradle/publishing.gradle" apply from: "tasks-gradle/dokka.gradle" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt new file mode 100644 index 00000000..d5a6e46d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.core.model + +import java.util.UUID + +class TransactionId { + private var transactionId: UUID = UUID.randomUUID() + + fun peek(): UUID { + return transactionId + } + + fun dequeue(): UUID { + return transactionId.also { transactionId = UUID.randomUUID() } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt index acaf8513..1742ecbb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt @@ -16,7 +16,7 @@ package com.expediagroup.sdk.core.model.paging import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.HeaderValue +import com.expediagroup.sdk.v2.core.constant.HeaderValue import com.expediagroup.sdk.core.model.Response import com.expediagroup.sdk.core.plugin.logging.GZipEncoder.decode import com.expediagroup.sdk.core.plugin.logging.contentEncoding diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt index 6dcc504e..867819d0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt @@ -15,7 +15,7 @@ */ package com.expediagroup.sdk.core.plugin.logging -import com.expediagroup.sdk.core.constant.LoggerName +import com.expediagroup.sdk.v2.core.constant.LoggerName import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider import com.expediagroup.sdk.core.model.getTransactionId import io.ktor.client.HttpClient diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt index a78021f4..06aa66ba 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt @@ -15,8 +15,8 @@ */ package com.expediagroup.sdk.core.plugin.logging -import com.expediagroup.sdk.core.constant.HeaderValue -import com.expediagroup.sdk.core.constant.LoggerName +import com.expediagroup.sdk.v2.core.constant.HeaderValue +import com.expediagroup.sdk.v2.core.constant.LoggerName import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider import com.expediagroup.sdk.core.model.getTransactionId import com.expediagroup.sdk.core.plugin.logging.GZipEncoder.decode diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt index b2f64d27..3bb3ab0f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -150,4 +150,3 @@ data class ClientConfiguration( ) } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt index 1e657b15..66e0a32a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt @@ -87,4 +87,4 @@ object EndpointProvider { ) } } -} +} \ No newline at end of file diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt new file mode 100644 index 00000000..f9230d4b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt @@ -0,0 +1,93 @@ +package com.expediagroup.sdk.v2.core.apache.util + +import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.core.trait.configuration.MaxConnectionsPerRouteTrait +import com.expediagroup.sdk.v2.core.trait.configuration.MaxConnectionsTotalTrait +import com.expediagroup.sdk.v2.core.trait.configuration.SocketTimeoutTrait +import com.google.api.client.http.apache.v2.ApacheHttpTransport +import org.apache.http.client.HttpClient +import org.apache.http.client.config.RequestConfig +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + + +fun createApacheHttpTransport(configuration: ClientConfiguration): ApacheHttpTransport = + ApacheHttpTransport(createHttpClient(configuration)) + +fun getSingletonApacheHttpTransport(configuration: ClientConfiguration): ApacheHttpTransport = + ApacheHttpTransportSingleton.getTransport(configuration) + + +/** + * Singleton object for managing Apache HTTP transports. + * + * This singleton object provides a thread-safe way to manage a collection of `ApacheHttpTransport` + * instances identified by a `ClientConfiguration`'s UUID. The transport instances are stored + * in a `ConcurrentMap` to prevent duplicate creation and ensure efficient reuse. + * + * Functions: + * - getTransport(configuration: ClientConfiguration): Retrieves an existing `ApacheHttpTransport` + * instance if available, or creates a new one based on the given `ClientConfiguration`. + */ +private object ApacheHttpTransportSingleton { + private val transports: ConcurrentMap = ConcurrentHashMap() + + + /** + * Retrieves or creates an `ApacheHttpTransport` instance based on the provided `ClientConfiguration`. + * + * If an `ApacheHttpTransport` associated with the given configuration ID already exists, it is returned. + * Otherwise, a new `ApacheHttpTransport` is created using the provided configuration. + * + * @param configuration The `ClientConfiguration` instance containing the necessary traits and configurations + * for initializing or retrieving the `ApacheHttpTransport`. + * @return An `ApacheHttpTransport` instance associated with the given `ClientConfiguration`. + */ + fun getTransport(configuration: ClientConfiguration): ApacheHttpTransport = + transports.getOrPut(configuration.id) { + createApacheHttpTransport(configuration) + } +} + +/** + * Creates an `HttpClient` instance configured according to the specified `ClientConfiguration`. + * + * The method ensures that the provided `configuration` implements the necessary traits + * (`MaxConnectionsTotalTrait` and `MaxConnectionsPerRouteTrait`) required for setting up + * the `HttpClient`. + * + * @param configuration The `ClientConfiguration` instance containing the necessary traits + * and configurations for initializing the `HttpClient`. + * @return An `HttpClient` instance configured based on the provided `configuration`. + */ +fun createHttpClient(configuration: ClientConfiguration): HttpClient { + require(configuration is MaxConnectionsTotalTrait) { "Configuration must implement MaxConnectionsTotalTrait" } + require(configuration is MaxConnectionsPerRouteTrait) { "Configuration must implement MaxConnectionsPerRouteTrait" } + + return ApacheHttpTransport.newDefaultHttpClientBuilder() + .setDefaultRequestConfig(createRequestConfig(configuration)) + .setMaxConnTotal((configuration as MaxConnectionsTotalTrait).getMaxConnectionsTotal()) + .setMaxConnPerRoute((configuration as MaxConnectionsPerRouteTrait).getMaxConnectionsPerRoute()) + .build() +} + +/** + * Creates a `RequestConfig` instance based on the specified `ClientConfiguration`. + * + * This method ensures that the provided `configuration` implements the necessary + * `SocketTimeoutTrait` required for configuring the socket timeout settings in + * the `RequestConfig`. + * + * @param configuration The `ClientConfiguration` instance providing the socket timeout settings. + * The configuration must implement `SocketTimeoutTrait`. + * @return A `RequestConfig` instance configured with the socket timeout properties from the specified configuration. + */ +internal fun createRequestConfig(configuration: ClientConfiguration): RequestConfig { + require(configuration is SocketTimeoutTrait) { "Configuration must implement SocketTimeoutTrait" } + + return RequestConfig.copy(RequestConfig.DEFAULT) + .setConnectTimeout(configuration.getSocketTimeout().toInt()) + .setSocketTimeout(configuration.getSocketTimeout().toInt()) + .build() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt new file mode 100644 index 00000000..07b18c2b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt @@ -0,0 +1,17 @@ +package com.expediagroup.sdk.v2.core.authentication.extension + +import com.expediagroup.sdk.v2.core.constant.Authentication +import com.google.auth.oauth2.OAuth2CredentialsWithRefresh +import java.time.Duration + +/** + * Configures the `OAuth2CredentialsWithRefresh.Builder` with default configuration settings. + * + * This method sets the `expirationMargin` and `refreshMargin` to default values defined by `Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS`. + * + * @return The `OAuth2CredentialsWithRefresh.Builder` instance with default configurations applied. + */ +fun OAuth2CredentialsWithRefresh.Builder.withDefaultConfigurations(): OAuth2CredentialsWithRefresh.Builder = apply { + expirationMargin = Duration.ofSeconds(Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS) + refreshMargin = Duration.ofSeconds(Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt new file mode 100644 index 00000000..12393464 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt @@ -0,0 +1,13 @@ +package com.expediagroup.sdk.v2.core.authentication.strategy + +import com.expediagroup.sdk.v2.core.trait.authentication.AuthenticationHandlerTrait + +/** + * Enumeration that represents different authentication strategies for a client configuration. + * + * @property handlerFactory A factory that creates an instance of the authentication handler + * trait for the specific strategy. + */ +enum class AuthenticationStrategy(val handlerFactory: AuthenticationHandlerTrait) { + BEARER(BearerAuthenticationHandlerFactory), +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt new file mode 100644 index 00000000..0307d688 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt @@ -0,0 +1,149 @@ +package com.expediagroup.sdk.v2.core.authentication.strategy + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException +import com.expediagroup.sdk.v2.core.constant.LoggingMessage +import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.v2.core.logging.LogMessageTag +import com.expediagroup.sdk.v2.core.request.initializer.SdkRequestInitializer +import com.expediagroup.sdk.v2.core.trait.authentication.AuthenticationHandlerTrait +import com.expediagroup.sdk.v2.core.trait.authentication.RefreshAccessTokenTrait +import com.expediagroup.sdk.v2.core.trait.configuration.AuthEndpointTrait +import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.core.trait.configuration.KeyTrait +import com.expediagroup.sdk.v2.core.trait.configuration.SecretTrait +import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest +import com.google.api.client.auth.oauth2.TokenRequest +import com.google.api.client.auth.oauth2.TokenResponse +import com.google.api.client.http.BasicAuthentication +import com.google.api.client.http.GenericUrl +import com.google.api.client.http.HttpRequestInitializer +import com.google.api.client.http.HttpTransport +import com.google.api.client.json.gson.GsonFactory +import com.google.auth.oauth2.AccessToken +import java.time.Instant +import java.util.* + +/** + * Factory object for creating Bearer Authentication handlers. + */ +object BearerAuthenticationHandlerFactory : AuthenticationHandlerTrait { + + /** + * Creates an authentication handler to refresh access tokens using client credentials. + * + * @param config The client configuration that must implement KeyTrait, SecretTrait, and AuthEndpointTrait. + * @param transport The HTTP transport to be used for token refresh requests. + * @return An instance of RefreshAccessTokenTrait which can be used to refresh access tokens. + */ + override fun createAuthenticationHandler( + config: ClientConfiguration, + transport: HttpTransport, + ): RefreshAccessTokenTrait { + + require(config is KeyTrait) { "Configuration must implement KeyTrait!" } + require(config is SecretTrait) { "Configuration must implement SecretTrait!" } + require(config is AuthEndpointTrait) { "Configuration must implement AuthEndpointTrait!" } + + return object : RefreshAccessTokenTrait { + private val logger = ExpediaGroupLoggerFactory.getLogger(javaClass) + + /** + * Refreshes and returns a new `AccessToken` by making a token request. + * This method builds the token request, initializes it, logs the token renewal progress, + * executes the request, and handles any exceptions that occur. + * + * @return A newly generated `AccessToken` instance if the token request is successful. + * @throws ExpediaGroupAuthException if the token renewal process fails. + */ + override fun refreshAccessToken(): AccessToken = + buildTokenRequest() + .also attachDefaultInitializer@{ request -> + request.requestInitializer = attachSdkRequestInitializer(request) + }.also logTokenRenewalInProgress@{ + logger.info(LoggingMessage.TOKEN_RENEWAL_IN_PROGRESS, LogMessageTag.PROGRESSING) + }.let executeRequest@{ request -> + try { + return@executeRequest buildAccessToken(request.execute()) + } catch (e: Exception) { + logger.error(LoggingMessage.TOKEN_RENEWAL_FAILURE, LogMessageTag.ERROR) + throw ExpediaGroupAuthException( + message = "Token renewal failed!", + cause = e + ) + } + } + + + /** + * Builds a `ClientCredentialsTokenRequest` using the configuration traits available in the `config` property. + * Key, secret, and authentication endpoint are retrieved from the configuration. + * + * @return An instance of `ClientCredentialsTokenRequest` configured with client authentication and other necessary parameters. + */ + private fun buildTokenRequest(): ClientCredentialsTokenRequest { + val key = (config as KeyTrait).getKey() + val secret = (config as SecretTrait).getSecret() + val authEndpoint = (config as AuthEndpointTrait).getAuthEndpoint() + + return ClientCredentialsTokenRequest( + transport, + GsonFactory.getDefaultInstance(), + GenericUrl(authEndpoint), + ).setClientAuthentication( + BasicAuthentication(key, secret) + ) + } + + /** + * Calculates the token expiration time based on the current time and the duration specified in the token response. + * + * @param response The `TokenResponse` object containing the `expiresInSeconds` property, which indicates the time in seconds + * for which the token is valid from the current time. + * @return A `Date` object representing the calculated expiration time of the token. + */ + private fun calculateTokenExpirationTime(response: TokenResponse): Date = + Date.from(Instant.now().plusSeconds(response.expiresInSeconds.toLong())) + + /** + * Builds an `AccessToken` object from the given `TokenResponse`. + * Logs a success message upon successful token renewal. + * + * @param response The `TokenResponse` from which the access token is created. + * @return An instance of `AccessToken` containing the token value, expiration time, and scopes. + */ + private fun buildAccessToken(response: TokenResponse): AccessToken = + AccessToken.newBuilder() + .setTokenValue(response.accessToken) + .setExpirationTime(calculateTokenExpirationTime(response)) + .setScopes(response.scope) + .build().also { + logger.info( + LoggingMessage.TOKEN_RENEWAL_SUCCESSFUL, + LogMessageTag.SUCCESS + ) + } + + /** + * Attaches an appropriate SDK request initializer to the given `TokenRequest` instance based on the type of + * `requestInitializer` present in the `TokenRequest`. + * + * @param request The `TokenRequest` object that requires an initializer. The function checks the type of + * `requestInitializer` within this request and attaches an appropriate `SdkRequestInitializer`. + * If no suitable initializer is found, a default `SdkRequestInitializer` is used. + */ + private fun attachSdkRequestInitializer(request: TokenRequest) = + when (request.requestInitializer) { + is SdkRequestInitializer -> + (request.requestInitializer as SdkRequestInitializer).add( + SdkRequestInitializer.default() + ) + + is HttpRequestInitializer -> + SdkRequestInitializer.default().add(request.requestInitializer) + + else -> SdkRequestInitializer.default() + } + } + + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt new file mode 100644 index 00000000..946e5321 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt @@ -0,0 +1,83 @@ +package com.expediagroup.sdk.v2.core.authentication.util + +import com.expediagroup.sdk.v2.core.apache.util.createApacheHttpTransport +import com.expediagroup.sdk.v2.core.authentication.extension.withDefaultConfigurations +import com.expediagroup.sdk.v2.core.trait.common.IdTrait +import com.expediagroup.sdk.v2.core.trait.configuration.* +import com.google.auth.http.HttpCredentialsAdapter +import com.google.auth.oauth2.AccessToken +import com.google.auth.oauth2.OAuth2CredentialsWithRefresh +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +/** + * Creates and returns an HttpCredentialsAdapter based on the given ClientConfiguration. + * The configuration must implement KeyTrait, SecretTrait, AuthEndpointTrait, and AuthenticationStrategyTrait. + * + * @param configuration The configuration object that must implement the necessary traits for authentication. + * @return An instance of HttpCredentialsAdapter initialized with the given configuration. + */ +fun getHttpCredentialsAdapter(configuration: ClientConfiguration): HttpCredentialsAdapter { + require(configuration is KeyTrait) { "Configuration must implement KeyTrait!" } + require(configuration is SecretTrait) { "Configuration must implement SecretTrait!" } + require(configuration is AuthEndpointTrait) { "Configuration must implement AuthEndpointTrait!" } + require(configuration is AuthenticationStrategyTrait) { "Configuration must implement ClientConfigurationTrait!" } + + return CreateSingletonHttpCredentialsAdapter.execute(configuration) +} + +/** + * A singleton class responsible for creating and managing instances of `HttpCredentialsAdapter` + * based on the provided `ClientConfiguration`. This class ensures that each configuration has a + * unique adapter instance. + */ +private class CreateSingletonHttpCredentialsAdapter private constructor() : + (ClientConfiguration) -> HttpCredentialsAdapter { + companion object { + @JvmStatic + private val INSTANCE = CreateSingletonHttpCredentialsAdapter() + + @JvmStatic + private val adapters: ConcurrentMap = ConcurrentHashMap() + + /** + * Invokes the creation or retrieval of an `HttpCredentialsAdapter` based on the provided `ClientConfiguration`. + * + * @param configuration The `ClientConfiguration` instance which must implement `KeyTrait`, `IdTrait`, + * and `AuthenticationStrategyTrait`. + * @return An instance of `HttpCredentialsAdapter` mapped to the unique ID of the provided configuration. + * @throws IllegalArgumentException If the provided configuration does not implement `KeyTrait`. + */ + @JvmStatic + fun execute(configuration: ClientConfiguration): HttpCredentialsAdapter = + INSTANCE(configuration) + } + + /** + * Invokes the creation or retrieval of an `HttpCredentialsAdapter` based on the provided `ClientConfiguration`. + * + * @param configuration The `ClientConfiguration` instance which must implement `KeyTrait`, `IdTrait`, + * and `AuthenticationStrategyTrait`. + * @return An instance of `HttpCredentialsAdapter` mapped to the unique ID of the provided configuration. + * @throws IllegalArgumentException If the provided configuration does not implement `KeyTrait`. + */ + override fun invoke(configuration: ClientConfiguration): HttpCredentialsAdapter { + require(configuration is KeyTrait) { "Configuration must implement KeyTrait!" } + + val strategy = (configuration as AuthenticationStrategyTrait).getAuthenticationStrategy() + val handler = strategy.handlerFactory.createAuthenticationHandler( + config = configuration, + transport = createApacheHttpTransport(configuration) + ) + + return adapters.getOrPut((configuration as IdTrait).id) { + HttpCredentialsAdapter( + OAuth2CredentialsWithRefresh.newBuilder() + .withDefaultConfigurations() + .setRefreshHandler { handler.refreshAccessToken() as AccessToken } + .build() + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt new file mode 100644 index 00000000..0bdb43fa --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt @@ -0,0 +1,43 @@ +package com.expediagroup.sdk.v2.core.client + +import com.expediagroup.sdk.v2.core.configuration.ExpediaGroupDefaultClientConfiguration +import com.expediagroup.sdk.v2.core.logging.mask.configureLogMasking +import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.core.trait.configuration.MaskedLoggingBodyFieldsTrait +import com.expediagroup.sdk.v2.core.trait.configuration.MaskedLoggingHeadersTrait +import com.google.api.client.googleapis.services.AbstractGoogleClient +import com.google.api.client.googleapis.services.AbstractGoogleClientRequest + + +/** + * ApiClient class is responsible for making API requests using an extended Google API client. + * + * @param builder The ApiClientBuilder instance used for building the API client. + * @param configuration The client configuration implementing `ClientConfiguration`. + * + * The ApiClient utilizes the provided builder to initialize and configure the API client. It ensures that + * the given configuration adheres to both `MaskedLoggingHeadersTrait` and `MaskedLoggingBodyFieldsTrait`. + * It configures log masking for the headers and body fields as specified by these traits. + */ +class ApiClient( + builder: ApiClientBuilder, + configuration: ClientConfiguration = ExpediaGroupDefaultClientConfiguration +) : AbstractGoogleClient(builder) { + init { + require(configuration is MaskedLoggingHeadersTrait) + require(configuration is MaskedLoggingBodyFieldsTrait) + + configureLogMasking((configuration as MaskedLoggingHeadersTrait).getMaskedLoggingHeaders()) + configureLogMasking((configuration as MaskedLoggingBodyFieldsTrait).getMaskedLoggingBodyFields()) + } + + /** + * Initializes the given HTTP client request with additional configurations. + * + * @param httpClientRequest The HTTP client request to be initialized. + * It can be an instance of `AbstractGoogleClientRequest`. + */ + override fun initialize(httpClientRequest: AbstractGoogleClientRequest<*>?) { + super.initialize(httpClientRequest) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt new file mode 100644 index 00000000..641f39e9 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt @@ -0,0 +1,84 @@ +package com.expediagroup.sdk.v2.core.client + +import com.apollographql.apollo.api.http.HttpHeader +import com.apollographql.apollo.api.http.HttpRequest +import com.apollographql.apollo.api.http.HttpResponse +import com.apollographql.apollo.exception.ApolloNetworkException +import com.apollographql.java.client.ApolloDisposable +import com.apollographql.java.client.network.http.HttpCallback +import com.apollographql.java.client.network.http.HttpEngine +import com.expediagroup.sdk.v2.core.request.Request +import com.google.api.client.http.HttpHeaders +import okio.buffer +import okio.source + + +/** + * Implementation of the `HttpEngine` interface that uses an `ApiClient` instance to execute HTTP requests. + * + * @constructor Creates an `ApiClientApolloHttpEngine` with the specified `ApiClient`. + * + * @param client The instance of `ApiClient` used to execute the HTTP requests. + */ +class ApiClientApolloHttpEngine( + private val client: ApiClient +) : HttpEngine { + /** + * Executes an HTTP request using the provided `ApiClient` and handles the response or failure via the specified callback. + * + * @param request The GraphQL HTTP request containing method, URL, body, and headers. + * @param callback The callback to handle the HTTP response or failure. + * @param disposable The disposable resource associated with the request, allowing cancellation if necessary. + */ + override fun execute(request: HttpRequest, callback: HttpCallback, disposable: ApolloDisposable) { + try { + Request( + client, + graphQLRequest = request, + responseType = Any::class.java + ).executeUnparsed().let { response -> + HttpResponse.Builder(statusCode = response.statusCode).apply { + body(response.content.source().buffer()) + headers(response.headers.toApolloHeaders()) + }.also { + callback.onResponse(it.build()) + } + } + } catch (e: Exception) { + callback.onFailure(ApolloNetworkException(platformCause = e)) + } + } + + /** + * Disposes of any resources held by the `ApiClientApolloHttpEngine`. + * + * This implementation is a no-op as there are no resources to be disposed of in this class. + * Typically, this method would be used to clean up resources, such as closing network connections. + */ + override fun dispose() { + // no-op + } + + /** + * Converts `HttpHeaders` into a list of `HttpHeader` objects. + * + * This method iterates through the key-value pairs in the `HttpHeaders`, converting each key-value pair + * into `HttpHeader` instances. + * If the value is an `Iterable`, each item is used to create an `HttpHeader`. + * Otherwise, a single `HttpHeader` with the value is created. + * + * @return A list of `HttpHeader` objects. + */ + private fun HttpHeaders.toApolloHeaders(): List { + val self = this@toApolloHeaders + + return buildList { + self.forEach { key, value -> + when (value) { + is Iterable<*> -> this.addAll(value.map { HttpHeader(key, value.toString()) }) + else -> this.add(HttpHeader(key, value.toString())) + } + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt new file mode 100644 index 00000000..30476fd2 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt @@ -0,0 +1,48 @@ +package com.expediagroup.sdk.v2.core.client + +import com.expediagroup.sdk.v2.core.configuration.SdkMetadata +import com.expediagroup.sdk.v2.core.request.initializer.ApiClientRequestInitializer +import com.google.api.client.googleapis.services.AbstractGoogleClient +import com.google.api.client.http.GenericUrl +import com.google.api.client.http.HttpRequestInitializer +import com.google.api.client.http.HttpTransport +import com.google.api.client.json.JsonFactory + +/** + * Builder for constructing an instance of `ApiClient`. This class extends + * `AbstractGoogleClient.Builder` and provides the necessary configurations + * for the API client. + * + * @param transport The HTTP transport to be used for the API client. + * @param jsonFactory Factory for JSON parsing and serialization. + * @param rootUrl The root URL for the API service. + * @param requestInitializer Optional request initializer for HTTP requests. + * @param servicePath The path to the service endpoint. + */ +class ApiClientBuilder( + transport: HttpTransport, + jsonFactory: JsonFactory, + rootUrl: GenericUrl, + requestInitializer: HttpRequestInitializer? = null, + servicePath: String = "", +) : AbstractGoogleClient.Builder( + transport, + rootUrl.build(), + servicePath, + jsonFactory.createJsonObjectParser(), // TODO: Configure value + requestInitializer, +) { + /** + * Builds an instance of `ApiClient` using the current configuration of `ApiClientBuilder`. + * + * @return An instance of `ApiClient` configured with the properties set in this builder. + */ + override fun build(): AbstractGoogleClient { + return ApiClient(this) + } + + init { + applicationName = SdkMetadata.getArtifactId() + googleClientRequestInitializer = ApiClientRequestInitializer.default() + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt new file mode 100644 index 00000000..b766ef6c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt @@ -0,0 +1,102 @@ +package com.expediagroup.sdk.v2.core.client + +import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.v2.core.request.Request +import com.expediagroup.sdk.v2.core.client.util.createApiClient +import com.expediagroup.sdk.v2.core.jackson.deserialize +import com.expediagroup.sdk.v2.core.model.Operation +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import java.io.InputStream + +/** + * SdkClient is a wrapper for creating and executing API requests. + * + * @param configuration The client configuration implementing `FullClientConfiguration`. + */ +class SdkClient( + configuration: FullClientConfiguration, +) { + val apiClient: ApiClient = createApiClient( + configuration = configuration, + ) + + /** + * Executes a given operation and deserializes the response into the specified type. + * + * @param T The type into which the response will be deserialized. + * @param operation The operation to be executed. + * @param enableGzipContent Flag to enable or disable GZip content. + * @param typeReference A reference to the type into which the response should be deserialized. + * @return The deserialized response of the operation, or null if the deserialization fails. + */ + inline fun execute( + operation: Operation<*>, + enableGzipContent: Boolean = false, + typeReference: TypeReference = jacksonTypeRef() + ): T? = + Request( + apiClient, + operation, + T::class.java + ).setDisableGZipContent( + enableGzipContent.not() + ).executeUnparsed().let { + deserialize(it.parseAsString(), typeReference) + } + + /** + * Executes a given operation and returns the response as an InputStream. + * + * @param operation The operation to be executed. + * @param enableGzipContent Flag to enable or disable GZip content. + * @return The InputStream representing the response of the operation, or null if the operation fails. + */ + fun executeAsInputStream(operation: Operation<*>, enableGzipContent: Boolean = false): InputStream? = + Request( + apiClient, + operation, + Any::class.java + ).apply { + setDisableGZipContent(enableGzipContent.not()) + }.executeAsInputStream() + + /** + * Executes a given operation without parsing the response and returns the raw result. + * + * @param operation The operation to be executed. + * @param enableGzipContent Flag to enable or disable GZip content. + * @return The raw response of the operation. + */ + fun executeUnparsed(operation: Operation<*>, enableGzipContent: Boolean = false): Any = + Request( + apiClient, + operation, + Any::class.java + ).apply { + setDisableGZipContent(enableGzipContent.not()) + }.executeUnparsed() + + /** + * Executes a given operation and downloads the response to an OutputStream. + * + * @param operation The operation to be executed. + * @param outputStream The OutputStream to which the response will be written. + * @param enableGzipContent Flag to enable or disable GZip content compression. + */ + fun executeAndDownloadTo( + operation: Operation<*>, + outputStream: java.io.OutputStream?, + enableGzipContent: Boolean = false + ) { + val request = Request( + apiClient, + operation, + Any::class.java + ).apply { + setDisableGZipContent(enableGzipContent.not()) + } + + request.executeAndDownloadTo(outputStream) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt new file mode 100644 index 00000000..b7d07515 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt @@ -0,0 +1,40 @@ +package com.expediagroup.sdk.v2.core.client.util + +import com.expediagroup.sdk.v2.core.apache.util.getSingletonApacheHttpTransport +import com.expediagroup.sdk.v2.core.authentication.util.getHttpCredentialsAdapter +import com.expediagroup.sdk.v2.core.client.ApiClient +import com.expediagroup.sdk.v2.core.client.ApiClientBuilder +import com.expediagroup.sdk.v2.core.request.initializer.SdkRequestInitializer +import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.core.trait.configuration.EndpointTrait +import com.google.api.client.http.GenericUrl +import com.google.api.client.http.HttpTransport + +/** + * Creates an instance of `ApiClient` using the provided namespace, client configuration, and optional HTTP transport. + * + * @param namespace The namespace to be used for creating the API client. + * @param configuration The client configuration implementing `ClientConfiguration` interface. Must also implement `EndpointTrait`. + * @param transport An optional `HttpTransport` object. If not provided, a default transport will be used. + * @return An instance of `ApiClient`. + */ +@JvmOverloads +fun createApiClient( + configuration: ClientConfiguration, + transport: HttpTransport? = null +): ApiClient { + val jsonFactory = createGsonFactory() + require(configuration is EndpointTrait) { "Configuration must implement EndpointTrait" } + + val requestInitializer = SdkRequestInitializer.default() + .add(getHttpCredentialsAdapter(configuration)) + + val builder = ApiClientBuilder( + transport = transport ?: getSingletonApacheHttpTransport(configuration), + jsonFactory = jsonFactory, + rootUrl = GenericUrl((configuration as EndpointTrait).getEndpoint()), + requestInitializer = requestInitializer, + ) + + return ApiClient(builder, configuration) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt new file mode 100644 index 00000000..5fc526c1 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt @@ -0,0 +1,18 @@ +package com.expediagroup.sdk.v2.core.client.util + +import com.google.api.client.json.gson.GsonFactory + +/** + * Creates a GsonFactory instance using the provided builder or returns the default instance if the builder is null. + * + * TODO: To be extended to hold Gson Factory configuration + * + * @param builder An optional builder for customizing the GsonFactory instance. If null, the default GsonFactory instance is returned. + * @return An instance of GsonFactory based on the builder configuration or the default configuration. + */ +internal fun createGsonFactory(builder: GsonFactory.Builder? = null) = + if (builder == null) { + GsonFactory.getDefaultInstance() + } else { + builder.build() + } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt new file mode 100644 index 00000000..e7f82c0e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt @@ -0,0 +1,75 @@ +package com.expediagroup.sdk.v2.core.configuration + + +/** + * A builder class for constructing instances of FullClientConfiguration with customizable parameters. + * This builder pattern allows you to configure various settings for the client, such as authentication details, + * endpoints, and timeout settings. + * + * @param T The type of the client that will be built. + */ +abstract class DefaultClientBuilder { + private var configurationBuilder = FullClientConfiguration.Builder() + + fun key(key: String) = apply { + configurationBuilder = configurationBuilder.key(key) + } + + fun secret(secret: String) = apply { + configurationBuilder = configurationBuilder.secret(secret) + } + + fun endpoint(endpoint: String) = apply { + configurationBuilder = configurationBuilder.endpoint(endpoint) + } + + fun authEndpoint(authEndpoint: String) = apply { + configurationBuilder = configurationBuilder.authEndpoint(authEndpoint) + } + + fun requestTimeout(requestTimeout: Long) = apply { + configurationBuilder = configurationBuilder.requestTimeout(requestTimeout) + } + + fun connectionTimeout(connectionTimeout: Long) = apply { + configurationBuilder = configurationBuilder.connectionTimeout(connectionTimeout) + } + + fun socketTimeout(socketTimeout: Long) = apply { + configurationBuilder = configurationBuilder.socketTimeout(socketTimeout) + } + + fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { + configurationBuilder = configurationBuilder.maskedLoggingHeaders(maskedLoggingHeaders) + } + + fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { + configurationBuilder = configurationBuilder.maskedLoggingBodyFields(maskedLoggingBodyFields) + } + + fun maxConnectionTotal(maxConnectionTotal: Int) = apply { + configurationBuilder = configurationBuilder.maxConnectionsTotal(maxConnectionTotal) + } + + fun maxConnectionPerRoute(maxConnectionPerRoute: Int) = apply { + configurationBuilder = configurationBuilder.maxConnectionsPerRoute(maxConnectionPerRoute) + } + + /** + * Builds and returns a fully configured client instance. + * + * @return a fully configured instance of the client. + */ + fun buildConfiguration(): FullClientConfiguration { + return configurationBuilder.build() + } + + /** + * Builds and returns a fully configured client instance. + * + * @return a fully configured instance of the client. + */ + open fun build(): T { + throw NotImplementedError("Not yet implemented") + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt new file mode 100644 index 00000000..bd0b4764 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt @@ -0,0 +1,45 @@ +package com.expediagroup.sdk.v2.core.configuration + +import com.expediagroup.sdk.v2.core.constant.Constant +import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy + +/** + * Implementation of `FullClientConfiguration` that provides default configuration values for the Expedia Group API client. + * + * This object acts as the default configuration and returns preset values for various configuration traits needed + * to interact with the Expedia Group API. + */ +object ExpediaGroupDefaultClientConfiguration : + FullClientConfiguration { + + override fun getKey(): String { + throw NotImplementedError("Not implemented") + } + + override fun getSecret(): String { + throw NotImplementedError("Not implemented") + } + + override fun getEndpoint(): String = "https://api.expediagroup.com/" + override fun getAuthEndpoint(): String = "${getEndpoint()}identity/oauth2/v3/token/" + override fun getAuthenticationStrategy(): AuthenticationStrategy = AuthenticationStrategy.BEARER + override fun getRequestTimeout(): Long = Constant.INFINITE_TIMEOUT + override fun getConnectionTimeout(): Long = Constant.TEN_SECONDS_IN_MILLIS + override fun getSocketTimeout(): Long = Constant.FIFTEEN_SECONDS_IN_MILLIS + override fun getMaxConnectionsTotal(): Int = Constant.MAX_CONNECTIONS_TOTAL + override fun getMaxConnectionsPerRoute(): Int = Constant.MAX_CONNECTIONS_PER_ROUTE + override fun getMaskedLoggingHeaders(): Set = setOf("Authorization") + override fun getMaskedLoggingBodyFields(): Set = setOf( + "cvv", + "pin", + "card_cvv", + "card_cvv2", + "card_number", + "access_token", + "security_code", + "account_number", + "card_avs_response", + "card_cvv_response", + "card_cvv2_response", + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt new file mode 100644 index 00000000..a200b1f7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt @@ -0,0 +1,137 @@ +package com.expediagroup.sdk.v2.core.configuration + +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException +import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.v2.core.trait.common.BuilderTrait +import com.expediagroup.sdk.v2.core.trait.configuration.* +import java.util.* + +/** + * Interface representing the full configuration required for a client setup. + * Combines various traits that offer specific configuration aspects such as key, secret, endpoints, + * timeout settings, logging preferences, and connection limitations. + */ +interface FullClientConfiguration : + KeyTrait, + SecretTrait, + EndpointTrait, + AuthEndpointTrait, + MaskedLoggingHeadersTrait, + MaskedLoggingBodyFieldsTrait, + RequestTimeoutTrait, + SocketTimeoutTrait, + ConnectionTimeoutTrait, + AuthenticationStrategyTrait, + MaxConnectionsTotalTrait, + MaxConnectionsPerRouteTrait { + open class Builder : BuilderTrait { + private var key: String? = null + private var secret: String? = null + private var endpoint: String? = null + private var authEndpoint: String? = null + private var requestTimeout: Long? = null + private var connectionTimeout: Long? = null + private var socketTimeout: Long? = null + private var maskedLoggingHeaders: Set? = null + private var maskedLoggingBodyFields: Set? = null + private var maxConnectionsTotal: Int? = null + private var maxConnectionsPerRoute: Int? = null + private var authenticationStrategy: String? = null + + fun key(key: String) = apply { + this.key = key + } + + fun secret(secret: String) = apply { + this.secret = secret + } + + fun endpoint(endpoint: String) = apply { + this.endpoint = endpoint + } + + fun authEndpoint(authEndpoint: String) = apply { + this.authEndpoint = authEndpoint + } + + fun requestTimeout(requestTimeout: Long) = apply { + this.requestTimeout = requestTimeout + } + + fun connectionTimeout(connectionTimeout: Long) = apply { + this.connectionTimeout = connectionTimeout + } + + fun socketTimeout(socketTimeout: Long) = apply { + this.socketTimeout = socketTimeout + } + + fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { + this.maskedLoggingHeaders = maskedLoggingHeaders + } + + fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { + this.maskedLoggingBodyFields = maskedLoggingBodyFields + } + + fun maxConnectionsTotal(maxConnectionsTotal: Int) = apply { + this.maxConnectionsTotal = maxConnectionsTotal + } + + fun maxConnectionsPerRoute(maxConnectionsPerRoute: Int) = apply { + this.maxConnectionsPerRoute = maxConnectionsPerRoute + } + + fun authenticationStrategy(authenticationStrategy: String) = apply { + this.authenticationStrategy = authenticationStrategy + } + + /** + * Builds and returns a fully configured instance of `FullClientConfiguration`. + * + * @return A `FullClientConfiguration` object with all required settings and defaults applied. + * @throws ExpediaGroupConfigurationException if required settings like key or secret are missing. + */ + override fun build(): FullClientConfiguration = + object : FullClientConfiguration { + override val id: UUID = UUID.randomUUID() + + override fun getKey(): String = + key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") + + + override fun getSecret(): String = + secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") + + override fun getEndpoint(): String = + endpoint ?: ExpediaGroupDefaultClientConfiguration.getEndpoint() + + override fun getAuthEndpoint(): String = + authEndpoint ?: ExpediaGroupDefaultClientConfiguration.getAuthEndpoint() + + override fun getMaskedLoggingHeaders(): Set = + maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() + + override fun getMaskedLoggingBodyFields(): Set = + maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() + + override fun getRequestTimeout(): Long = + requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() + + override fun getSocketTimeout(): Long = + socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() + + override fun getConnectionTimeout(): Long = + connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() + + override fun getAuthenticationStrategy(): AuthenticationStrategy = + ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() + + override fun getMaxConnectionsTotal(): Int = + maxConnectionsTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() + + override fun getMaxConnectionsPerRoute(): Int = + maxConnectionsPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt new file mode 100644 index 00000000..02708729 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt @@ -0,0 +1,27 @@ +package com.expediagroup.sdk.v2.core.configuration + +import com.google.common.io.Resources +import java.io.FileInputStream +import java.util.Properties + +internal object SdkMetadata { + private lateinit var artifactId: String + private lateinit var version: String + private lateinit var userAgentPrefix: String + + fun getArtifactId(): String = artifactId + fun getVersion(): String = version + fun getUserAgentPrefix(): String = userAgentPrefix + + init { + Resources.getResource("sdk.properties").file?.let { path -> + Properties().apply { + load(FileInputStream(path)) + }.also { properties -> + artifactId = properties.getProperty("artifactId") + version = properties.getProperty("version") + userAgentPrefix = properties.getProperty("userAgentPrefix") + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt new file mode 100644 index 00000000..c931a205 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt @@ -0,0 +1,5 @@ +package com.expediagroup.sdk.v2.core.constant + +object Authentication { + const val BEARER_EXPIRY_MARGIN_IN_SECONDS: Long = 10 +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt new file mode 100644 index 00000000..2618a512 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +import io.ktor.client.plugins.HttpTimeout + +internal object Constant { + const val EMPTY_STRING = "" + const val NEWLINE = "\n" + const val COMMA_SPACE = ", " + const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" + const val TEN_SECONDS_IN_MILLIS = 10_0000L + const val FIFTEEN_SECONDS_IN_MILLIS = 150_000L + const val INFINITE_TIMEOUT = HttpTimeout.INFINITE_TIMEOUT_MS + + private const val SUCCESSFUL_STATUS_CODES_RANGE_START = 200 + private const val SUCCESSFUL_STATUS_CODES_RANGE_END = 299 + val SUCCESSFUL_STATUS_CODES_RANGE: IntRange = SUCCESSFUL_STATUS_CODES_RANGE_START..SUCCESSFUL_STATUS_CODES_RANGE_END + + const val MAX_CONNECTIONS_TOTAL: Int = 500 + const val MAX_CONNECTIONS_PER_ROUTE: Int = 100 +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt new file mode 100644 index 00000000..b7671c48 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object ExceptionMessage { + const val AUTHENTICATION_FAILURE = "Unable to authenticate" + + const val AUTHENTICATION_NOT_CONFIGURED_FOR_CLIENT = "Authentication is not configured" + + const val LOGGING_MASKED_FIELDS_NOT_CONFIGURED_FOR_CLIENT = "Logging masked fields is not configured" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt new file mode 100644 index 00000000..3ae20666 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object HeaderKey { + const val PAGINATION_TOTAL_RESULTS = "pagination-total-results" + + const val LINK = "link" + + const val TRANSACTION_ID = "transaction-id" + + const val X_SDK_TITLE = "x-sdk-title" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt new file mode 100644 index 00000000..e5627a05 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object HeaderValue { + const val GZIP = "gzip" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt new file mode 100644 index 00000000..4536f84d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object Key { + const val CLIENT_KEY = "client_key" + + const val CLIENT_SECRET = "client_secret" + + const val ENDPOINT = "endpoint" + + const val AUTH_ENDPOINT = "auth_endpoint" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt new file mode 100644 index 00000000..479cbed7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +import com.google.auth.http.AuthHttpConstants + +internal data object LogMaskingFields { + val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(AuthHttpConstants.AUTHORIZATION) + val DEFAULT_MASKED_BODY_FIELDS: Set = + setOf( + "cvv", + "pin", + "card_cvv", + "card_cvv2", + "card_number", + "access_token", + "security_code", + "account_number", + "card_avs_response", + "card_cvv_response", + "card_cvv2_response" + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt new file mode 100644 index 00000000..f1eadeee --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object LoggerName { + const val REQUEST_BODY_LOGGER: String = "RequestBodyLogger" + const val RESPONSE_BODY_LOGGER: String = "ResponseBodyLogger" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt new file mode 100644 index 00000000..04928f2c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.constant + +internal object LoggingMessage { + const val LOGGING_PREFIX = "ExpediaSDK:" + + const val TOKEN_RENEWAL_IN_PROGRESS = "Renewing token" + + const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" + + const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" + + const val OMITTED = "<-- omitted -->" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt new file mode 100644 index 00000000..8c4478d7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt @@ -0,0 +1,21 @@ +package com.expediagroup.sdk.v2.core.http + +import org.apache.tika.Tika + +/** + * A utility class for detecting the type of binary large objects (BLOBs) using Apache Tika. + * + * This class is a singleton, which means it restricts the instantiation to one object. + * It inherits from the Tika class provided by Apache Tika library. + */ +class BlobTypeDetector private constructor(): Tika() { + companion object { + @JvmStatic + private val tika = Tika() + + @JvmStatic + fun getInstance(): BlobTypeDetector { + return BlobTypeDetector() + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt new file mode 100644 index 00000000..665048ad --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt @@ -0,0 +1,7 @@ +package com.expediagroup.sdk.v2.core.jackson + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + +val OBJECT_MAPPER = jacksonObjectMapper { + // TODO: Add custom configurations here +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt new file mode 100644 index 00000000..13462d62 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt @@ -0,0 +1,25 @@ +package com.expediagroup.sdk.v2.core.jackson + +import com.fasterxml.jackson.core.type.TypeReference + +/** + * Serializes the given value into its JSON string representation. + * + * @param value The object to be serialized. + * @return The JSON string representation of the given object. + */ +fun serialize(value: Any): String { + return OBJECT_MAPPER.writeValueAsString(value) +} + +/** + * Deserializes a JSON string into an object of the specified type. + * + * @param T The type into which the JSON string will be deserialized. + * @param json The JSON string to deserialize. + * @param typeReference A reference to the type into which the JSON string should be deserialized. + * @return The deserialized object of the specified type. + */ +inline fun deserialize(json: String, typeReference: TypeReference): T { + return OBJECT_MAPPER.readValue(json, typeReference) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt new file mode 100644 index 00000000..9d94bf4b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.logging + +import com.expediagroup.sdk.v2.core.constant.Constant +import com.expediagroup.sdk.v2.core.constant.LoggingMessage.LOGGING_PREFIX +import com.expediagroup.sdk.v2.core.logging.mask.maskLogs +import org.slf4j.Logger + +/** + * The ExpediaGroupLogger class is a decorator for the Logger interface that enhances log messages + * with additional formatting and tagging functionality. + * + * @param logger The underlying Logger instance to delegate logging actions to. + */ +internal class ExpediaGroupLogger(private val logger: Logger) : Logger by logger { + override fun info(msg: String) = logger.info(decorate(msg)) + + fun info(msg: String, vararg tags: LogMessageTag) = logger.info(decorate(msg, tags.toSet())) + + override fun warn(msg: String) = logger.warn(decorate(msg)) + + fun warn(msg: String, vararg tags: LogMessageTag) = logger.warn(decorate(msg, tags.toSet())) + + override fun debug(msg: String) = logger.debug(decorate(msg)) + + fun debug(msg: String, vararg tags: LogMessageTag) = logger.debug(decorate(msg, tags.toSet())) + + override fun error(msg: String) = logger.error(decorate(msg)) + + fun error(msg: String, vararg tags: LogMessageTag) = logger.error(decorate(msg, tags.toSet())) + + /** + * Normalizes a log message by applying specific formatting and tagging. + * + * @param msg The log message to normalize. + * @param tags A set of tags to include in the normalized message. + * @return A formatted and tagged log message. + */ + private fun normalize(msg: String, tags: Set = emptySet()): String = + buildList { + maskLogs(msg).trim().split(Constant.NEWLINE).forEach { line -> + tags.joinToString(Constant.COMMA_SPACE).let { tagsPrefix -> + if (tagsPrefix.isNotBlank()) "[$tagsPrefix]" else "" + }.also { + add("${Constant.DOUBLE_RIGHT_ANGLE_BRACKETS} $it $line".trim()) + } + } + }.joinToString(Constant.NEWLINE) + + /** + * Decorates a log message by prefixing it with a logging prefix and normalizing its format using provided tags. + * + * @param msg The message to be decorated. + * @param tags A set of tags to include in the decorated message. Defaults to an empty set. + * @return The decorated log message with the logging prefix and normalized format. + */ + private fun decorate(msg: String, tags: Set = emptySet()): String = "$LOGGING_PREFIX\n${normalize(msg, tags)}".trim() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt new file mode 100644 index 00000000..7e1d3408 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.logging + +import org.slf4j.LoggerFactory + +/** + * Factory object for creating instances of ExpediaGroupLogger. + */ +internal object ExpediaGroupLoggerFactory { + fun getLogger(clazz: Class<*>) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz)) + + fun getLogger( + clazz: Class<*>, + client: Any + ) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz)) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt new file mode 100644 index 00000000..d23615c4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt @@ -0,0 +1,23 @@ +package com.expediagroup.sdk.v2.core.logging + +/** + * Object holding constants for logging messages related to HTTP requests and responses. + */ +object LogMessageConstant { + const val REQUEST_HEADERS: String = "Request Headers:" + + const val REQUEST_BODY: String = "Request Body:" + + const val RESPONSE_HEADERS: String = "Response Headers:" + + const val RESPONSE_BODY: String = "Response Body:" + + const val EMPTY_OR_UNKNOWN_RESPONSE_BODY: String = "Empty response body or unknown content length" + + const val BODY_CONTENT_TYPE_NOT_SUPPORTED = "Content type %s not supported for logging" + + const val RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE = "Response too large to be logged whole..." + + const val RESPONSE_CONTENT_INPUT_STREAM_DOES_NOT_SUPPORT_MARK = + "Response content input stream does not support mark" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt new file mode 100644 index 00000000..ae631024 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.v2.core.logging + +/** + * Enumeration representing the different tags available for log messages. + * + * @property tag The string representation of the log message tag. + */ +enum class LogMessageTag(val tag: String) { + PROGRESSING("PROGRESSING"), + SUCCESS("SUCCESS"), + ERROR("ERROR"), + INCOMING("INCOMING"), + OUTGOING("OUTGOING"); + + override fun toString(): String = tag +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt new file mode 100644 index 00000000..c1f040b3 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt @@ -0,0 +1,21 @@ +package com.expediagroup.sdk.v2.core.logging + +import org.apache.http.entity.ContentType + +/** + * A list of MIME types representing content types that are deemed loggable. + * This collection is used to determine whether the content of HTTP requests or responses + * can be logged based on their MIME types. + */ +val LOGGABLE_CONTENT_TYPES = buildList { + add(ContentType.APPLICATION_ATOM_XML.mimeType) + add(ContentType.APPLICATION_JSON.mimeType) + add(ContentType.APPLICATION_XML.mimeType) + add(ContentType.APPLICATION_FORM_URLENCODED.mimeType) + add(ContentType.APPLICATION_SOAP_XML.mimeType) + add(ContentType.APPLICATION_XHTML_XML.mimeType) + add(ContentType.TEXT_PLAIN.mimeType) + add(ContentType.TEXT_HTML.mimeType) + add(ContentType.TEXT_XML.mimeType) + add(ContentType.DEFAULT_TEXT.mimeType) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt new file mode 100644 index 00000000..0c0288b4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt @@ -0,0 +1,19 @@ +package com.expediagroup.sdk.v2.core.logging.mask + +import com.ebay.ejmask.core.BaseFilter + +/** + * A filter class that extends the BaseFilter to apply masking on specific JSON fields using + * `ExpediaGroupJsonFieldPatternBuilder` for pattern building. + * + * This filter helps in masking sensitive JSON fields by replacing them with a predefined pattern. + * + * @constructor + * Initializes ExpediaGroupJsonFieldFilter with the specified fields to be masked. + * + * @param maskedFields An array of strings representing the names of the fields to be masked. + */ +internal class ExpediaGroupJsonFieldFilter(maskedFields: Array) : BaseFilter( + ExpediaGroupJsonFieldPatternBuilder::class.java, + *maskedFields +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt new file mode 100644 index 00000000..5d10eb30 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt @@ -0,0 +1,17 @@ +package com.expediagroup.sdk.v2.core.logging.mask + +import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder +import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED + +/** + * A builder class for creating JSON field replacement patterns specifically for Expedia Group. + * + * This class extends the `JsonFieldPatternBuilder` and provides an implementation for building + * replacement patterns for JSON field masking. + * + * The replacement pattern format generated by this builder is structured to conceal sensitive + * data while keeping a specified number of characters visible. + */ +internal class ExpediaGroupJsonFieldPatternBuilder : JsonFieldPatternBuilder() { + override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = "\"$1$2$OMITTED\"" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt new file mode 100644 index 00000000..062a2131 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt @@ -0,0 +1,100 @@ +package com.expediagroup.sdk.v2.core.logging.mask + +import com.ebay.ejmask.core.BaseFilter +import com.ebay.ejmask.core.EJMask +import com.ebay.ejmask.core.EJMaskInitializer +import com.ebay.ejmask.core.util.LoggerUtil + +/** + * Masks sensitive information within the provided log string. + * + * @param logs The log string that may contain sensitive information requiring masking. + * @return A new log string with sensitive information masked. + */ +fun maskLogs(logs: String): String { + return MaskLogs.execute(logs) +} + +/** + * Configures log masking by adding specified fields to the mask list. + * + * This function integrates with the log masking system to include additional fields + * that should be masked in the logs. The fields provided in the parameter are added + * to the set of fields that will have their values masked when logs are generated. + * + * @param fields The set of field names that need to be masked in the logs. + */ +fun configureLogMasking(fields: Set) { + MaskLogs.addFields(fields) +} + +/** + * Checks if a specified field is among the fields that should be masked. + * + * @param field The name of the field to check. + * @return `true` if the field should be masked, `false` otherwise. + */ +fun isMaskedField(field: String): Boolean { + return MaskLogs.maskedFields.contains(field) +} + +/** + * A utility class for masking sensitive information in log strings. + * + * The `MaskLogs` class is designed to replace sensitive information within logs with masked values. + * The class implements the `Function1` interface, enabling it to be invoked with a log string + * to produce a masked version of the string. + * + * The masking process relies on predefined filters that determine which fields within the log + * should be masked. Filters can be added and configured using the companion object's methods. + */ +private class MaskLogs : (String) -> String { + companion object { + @JvmStatic + val filters: MutableList = mutableListOf() + + val maskedFields: MutableSet = mutableSetOf() + + @JvmStatic + val INSTANCE = MaskLogs() + + /** + * Executes the masking process on the provided log string. + * + * @param logs The log string that may contain sensitive information requiring masking. + */ + @JvmStatic + fun execute(logs: String) = INSTANCE(logs) + + /** + * Adds specified fields to the list of fields to be masked in logs. + * + * The fields provided in the parameter are added to the internal set of fields + * and corresponding filters are created and added to the filter list. These filters + * are then integrated into the masking system to ensure the specified fields are + * masked in any logs they appear in. + * + * @param fields The set of field names that need to be masked in the logs. + */ + @JvmStatic + fun addFields(fields: Set) { + maskedFields.addAll(fields) + filters.add(ExpediaGroupJsonFieldFilter(fields.toTypedArray())) + filters.forEach { EJMaskInitializer.addFilter(it) } + } + } + + init { + LoggerUtil.register { _, _, _ -> /* disable logging */ } + } + + /** + * Masks the given text using the EJMask utility. + * + * @param text The input text that needs to be masked. + * @return The masked version of the input text. + */ + override fun invoke(text: String): String { + return EJMask.mask(text) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt new file mode 100644 index 00000000..ef1f6786 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +import com.expediagroup.sdk.v2.core.constant.HeaderKey +import io.ktor.http.* + +/** Get transaction id from headers. */ +fun Headers.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) + +/** Get transaction id from headers builder. */ +fun HeadersBuilder.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt new file mode 100644 index 00000000..0daa0eff --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +/** + * A representation of nothingness. Philosophers have debated the existence of nothing for centuries, but we have finally found it. + */ +data object Nothing diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt new file mode 100644 index 00000000..5879f1e7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +import com.google.api.client.http.HttpContent +import com.expediagroup.sdk.core.model.TransactionId + +abstract class Operation( + val url: String, + val method: String, + val operationId: String, + val requestBody: T?, + val params: OperationParams?, + val isUpload: Boolean = false, +) { + var transactionId: TransactionId = TransactionId() + private set + + open fun getHttpContent(): HttpContent? = null +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt new file mode 100644 index 00000000..ee296b99 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +interface OperationParams { + fun getHeaders(): Map? + fun getQueryParams(): Map>? + fun getPathParams(): Map +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt new file mode 100644 index 00000000..7b85c121 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.URL + +/** A model of "*.properties" file with some handy methods. */ +class Properties(private val data: Map) { + companion object { + /** Creates a new SdkProperties with the given data. */ + fun from(path: URL) = + Properties( + java.util.Properties().apply { + load(BufferedReader(InputStreamReader(path.openStream()))) + }.map { it.key.toString() to it.value.toString() }.toMap() + ) + } + + /** Returns the data for a given [key]. */ + operator fun get(key: String): String? = data[key] +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt new file mode 100644 index 00000000..b266503e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("unused") + +package com.expediagroup.sdk.v2.core.model + +import java.util.stream.Collectors +import kotlin.collections.Map.Entry + +/** + * A Generic Response to represent the response from a service call. + * + * @property statusCode The HTTP status code of the response + * @property data The body of the response + * @property headers The headers of the response + */ +@Suppress("MemberVisibilityCanBePrivate") +open class Response( + val statusCode: Int, + val data: T, + val headers: Map> +) { + constructor(statusCode: Int, data: T, headers: Set>>) : this( + statusCode, + data, + toHeadersMap(headers) + ) + + companion object { + @JvmStatic + fun toHeadersMap(headers: Set>>): Map> = + headers.stream().collect( + Collectors.toMap( + Entry>::key, + Entry>::value + ) + ) + } + + override fun toString() = "Response(statusCode=$statusCode, data=$data, headers=$headers)" + + @Deprecated("Use getData() instead", replaceWith = ReplaceWith("getData()")) + fun getBody() = data +} + +class EmptyResponse( + statusCode: Int, + headers: Map> +) : Response(statusCode, Nothing, headers) { + constructor(statusCode: Int, headers: Set>>) : this(statusCode, toHeadersMap(headers)) + + override fun toString(): String = "EmptyResponse(statusCode=$statusCode, headers=$headers)" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt new file mode 100644 index 00000000..879411a7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model + +import java.util.UUID + +class TransactionId { + private var transactionId: UUID = UUID.randomUUID() + + fun peek(): UUID { + return transactionId + } + + fun dequeue(): UUID { + return transactionId.also { transactionId = UUID.randomUUID() } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt new file mode 100644 index 00000000..8d21bb5a --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt @@ -0,0 +1,20 @@ +package com.expediagroup.sdk.v2.core.model + +import com.expediagroup.sdk.v2.core.configuration.SdkMetadata + +/** + * Data class representing a user agent which contains information about the Java Development Kit (JDK) version, + * operating system, SDK namespace, and SDK version. + * + * @param jdkVersion the version of the Java Development Kit (JDK) being used. + * @param operatingSystem the operating system on which the application is running. + */ +data class UserAgent( + val jdkVersion: String, + val operatingSystem: String, +) { + private val userAgentPrefix: String = SdkMetadata.getUserAgentPrefix() + + override fun toString(): String = + "$userAgentPrefix ($jdkVersion; $operatingSystem)" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt new file mode 100644 index 00000000..fa010e26 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception + +/** + * A base exception for all ExpediaGroup exceptions. + * + * @param message An optional error message. + * @param cause An optional cause of the error. + */ +open class ExpediaGroupException( + message: String? = null, + cause: Throwable? = null +) : RuntimeException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt new file mode 100644 index 00000000..988da1f4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.client + +import com.expediagroup.sdk.v2.core.model.exception.ExpediaGroupException + +/** + * An exception that is thrown when a client error occurs. + * + * @param message An optional message. + * @param cause An optional cause. + */ +open class ExpediaGroupClientException( + message: String? = null, + cause: Throwable? = null +) : ExpediaGroupException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt new file mode 100644 index 00000000..73dd88e0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.client + +/** + * An exception that is thrown when a configuration error occurs. + * + * @param message An optional error message. + * @param cause An optional cause of the error. + */ +class ExpediaGroupConfigurationException( + message: String? = null, + cause: Throwable? = null +) : ExpediaGroupClientException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt new file mode 100644 index 00000000..86f9aad0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.client + +/** + * Thrown to indicate that one or more passed field names are invalid. + * + * @param invalidFields the names of the invalid fields. + */ +class ExpediaGroupInvalidFieldNameException(invalidFields: Collection) : + ExpediaGroupClientException( + "All fields names must contain only alphanumeric characters in addition to - and _ but found [${ + invalidFields.joinToString( + "," + ) + }]" + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt new file mode 100644 index 00000000..d7626506 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.service + +import io.ktor.http.* + +/** + * An exception that is thrown when an authentication error occurs. + * + * @param message The error message. + * @param cause The cause of the error. + * @param transactionId The transaction-id of the auth request. + */ +class ExpediaGroupAuthException( + message: String? = null, + cause: Throwable? = null, + transactionId: String? = null +) : ExpediaGroupServiceException(message, cause, transactionId) { + /** + * An exception that is thrown when an authentication error occurs. + * + * @param errorCode The HTTP status code of the error. + * @param message The error message. + */ + constructor( + errorCode: HttpStatusCode, + message: String, + transactionId: String? + ) : this(message = "[${errorCode.value}] $message", transactionId = transactionId) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt new file mode 100644 index 00000000..e51c1ea1 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.service + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupApiException + +class ExpediaGroupServiceDefaultErrorException(code: Int, override val errorObject: String, transactionId: String?) : + ExpediaGroupApiException(code, errorObject, transactionId) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt new file mode 100644 index 00000000..4a26fe4a --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.expediagroup.sdk.v2.core.model.exception.service + +import com.expediagroup.sdk.v2.core.model.exception.ExpediaGroupException + +/** + * An exception that is thrown when a service error occurs. + * + * @param message An optional error message. + * @param cause An optional cause of the error. + */ +open class ExpediaGroupServiceException( + message: String? = null, + cause: Throwable? = null, + val transactionId: String? = null +) : ExpediaGroupException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt new file mode 100644 index 00000000..388f17d2 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt @@ -0,0 +1,246 @@ +package com.expediagroup.sdk.v2.core.request + +import com.expediagroup.sdk.v2.core.jackson.deserialize +import com.expediagroup.sdk.v2.core.model.Operation +import com.fasterxml.jackson.core.type.TypeReference +import com.google.api.client.googleapis.services.AbstractGoogleClient +import com.google.api.client.googleapis.services.AbstractGoogleClientRequest +import com.google.api.client.http.HttpContent +import com.google.api.client.http.HttpHeaders +import com.google.api.client.http.HttpResponse +import com.google.api.client.http.InputStreamContent +import okio.Buffer +import java.io.InputStream +import java.io.OutputStream +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor + +/** + * Represents a request to be executed by the Google client. + * + * @param ResponseType The type of response expected from the execution of this request. + * @param client The Google client to be used to execute the request. + * @param method The HTTP method to be used for the request (e.g., GET, POST). + * @param url The URL to which the request will be sent. + * @param content The content to be sent with the request. + * @param headers The HTTP headers to be included in the request, defaults to empty headers. + * @param queryParams The query parameters to be included in the request, defaults to empty map. + * @param responseType The class of the response type. + * @param disableGzipContent Flag to enable or disable GZip content compression, defaults to true. + */ +class Request( + private val client: AbstractGoogleClient, + method: String, + url: String, + content: HttpContent?, + headers: HttpHeaders? = HttpHeaders(), + queryParams: Map>? = emptyMap(), + responseType: Class, + disableGzipContent: Boolean = true +) : AbstractGoogleClientRequest( + client, + method, + url, + content, + responseType +) { + /** + * Constructs a new instance of the Request class using an AbstractGoogleClient and an Operation instance. + * + * @param client The Google client to be used for the request. + * @param operation The operation to be executed, encapsulating the HTTP method, URL, content, headers, and query parameters. + * @param responseType The type into which the response will be deserialized. + */ + constructor( + client: AbstractGoogleClient, + operation: Operation<*>, + responseType: Class + ) : this( + client = client, + method = operation.method, + url = operation.url, + content = operation.getHttpContent(), + headers = HttpHeaders().apply { operation.params?.getHeaders()?.let { putAll(it) } }, + queryParams = operation.params?.getQueryParams(), + responseType = responseType + ) + + /** + * Constructs a new instance of the `Request` class using the provided Google API client, + * GraphQL HTTP request, and the expected response type. + * + * @param client The Google API client used for making HTTP requests. + * @param graphQLRequest The GraphQL HTTP request containing method, URL, body, and headers. + * @param responseType The class of the expected response type. + */ + constructor( + client: AbstractGoogleClient, + graphQLRequest: com.apollographql.apollo.api.http.HttpRequest, + responseType: Class + ) : this( + client = client, + method = graphQLRequest.method.toString().uppercase(), + url = graphQLRequest.url, + content = graphQLRequest.body?.let { + val buffer = Buffer() + it.writeTo(buffer) + + return@let InputStreamContent(graphQLRequest.body?.contentType, buffer.inputStream()) + }, + headers = graphQLRequest.headers.let { + return@let HttpHeaders().apply { + it.forEach { (key, value) -> put(key, value) } + } + }, + responseType = responseType + ) + + init { + this.disableGZipContent = disableGzipContent + client.googleClientRequestInitializer.initialize(this) + + headers?.forEach { (key, value) -> + requestHeaders.put(key, value) + } + + queryParams?.forEach { (key, value) -> + put(key, value) + } + } + + /** + * Executes a request and parses the response into the specified type. + * + * @param T The type into which the response should be parsed. + * @param responseTypeReference A reference to the type into which the response should be parsed. + * @return The parsed response as an object of type T. + */ + inline fun executeAndParseAs(responseTypeReference: TypeReference): T { + return deserialize(executeUnparsed().parseAsString(), responseTypeReference) + } + + /** + * Executes a request asynchronously and parses the response into the specified type. + * + * @param T The type into which the response will be parsed. + * @param responseTypeReference A reference to the type into which the response should be parsed. + * @return A CompletableFuture representing pending completion of the parsing operation, + * with the parsed response of the specified type. + */ + inline fun executeAndParseAsAsync(responseTypeReference: TypeReference): CompletableFuture { + return CompletableFuture.supplyAsync({ executeAndParseAs(responseTypeReference) }) + } + + /** + * Executes a request and parses the response into the specified type asynchronously using the provided executor. + * + * @param T The type into which the response should be parsed. + * @param responseTypeReference A reference to the type into which the response should be parsed. + * @param executor The executor to use for asynchronous execution. + * @return A CompletableFuture containing the parsed response as an object of type T. + */ + inline fun executeAndParseAsAsync( + responseTypeReference: TypeReference, + executor: Executor + ): CompletableFuture { + return CompletableFuture.supplyAsync({ executeAndParseAs(responseTypeReference) }, executor) + } + + /** + * Executes a media request and returns the result as an InputStream. + * + * @return An InputStream containing the media data, or null if the request failed. + */ + public override fun executeMediaAsInputStream(): InputStream? { + return super.executeMediaAsInputStream() + } + + /** + * Executes a media request asynchronously and returns the result as an InputStream. + * + * @return A CompletableFuture containing an InputStream with the media data, or null if the request failed. + */ + fun executeMediaAsInputStreamAsync(): CompletableFuture = + CompletableFuture.supplyAsync(::executeMediaAsInputStream) + + /** + * Executes a media request asynchronously using the provided executor and returns the result as an InputStream. + * + * @param executor The executor to use for the asynchronous execution. + * @return A CompletableFuture containing an InputStream with the media data, or null if the request failed. + */ + fun executeMediaAsInputStreamAsync(executor: Executor): CompletableFuture = + CompletableFuture.supplyAsync(::executeMediaAsInputStream, executor) + + /** + * Executes a request asynchronously without parsing the response. + * + * @param executor The executor to use for asynchronous execution. + * @return A CompletableFuture containing the HttpResponse. + */ + fun executeUnparsedAsync(executor: Executor): CompletableFuture = + CompletableFuture.supplyAsync(::executeUnparsed, executor) + + /** + * Executes a request asynchronously without parsing the response. + * + * @return A CompletableFuture containing the HttpResponse. + */ + fun executeUnparsedAsync(): CompletableFuture = + CompletableFuture.supplyAsync(::executeUnparsed) + + /** + * Executes a request and downloads the response to the specified OutputStream asynchronously. + * + * @param outputStream The OutputStream to which the response will be downloaded. Can be null. + * @return A CompletableFuture representing the pending completion of the download operation. + */ + fun executeAndDownloadToAsync(outputStream: OutputStream?) = + CompletableFuture.supplyAsync { super.executeAndDownloadTo(outputStream) } + + /** + * Executes a request and downloads the response to the specified OutputStream asynchronously using the provided executor. + * + * @param outputStream The OutputStream to which the response will be downloaded. Can be null. + * @param executor The executor to use for asynchronous execution. + * @return A CompletableFuture representing the pending completion of the download operation. + */ + fun executeAndDownloadToAsync(outputStream: OutputStream?, executor: Executor) = + CompletableFuture.supplyAsync({ super.executeAndDownloadTo(outputStream) }, executor) + + /** + * Executes a request without parsing the response and returns an `HttpResponse`. + * + * This method overrides the `executeUnparsed` method from a superclass, executing the request and handling the response. + * If the response indicates an error, it throws an `ExpediaGroupServiceException`. + * Any other exceptions encountered during the execution are also caught and rethrown as `ExpediaGroupServiceException`. + * + * @return The unparsed `HttpResponse` from the executed request. + * @throws ExpediaGroupServiceException if the response indicates an error or if any exception occurs during execution. + */ + override fun executeUnparsed(): HttpResponse { + try { + return super.executeUnparsed().also { + throwOnError(it) + } + } catch (e: Exception) { + throw com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException( + message = e.message + ) + } + } + + /** + * Checks the `HttpResponse` for an error status and throws an `ExpediaGroupServiceException` if an error is detected. + * + * @param response The `HttpResponse` to check for errors. + * @throws ExpediaGroupServiceException if the response status code indicates an error. + */ + private fun throwOnError(response: HttpResponse) { + if (!response.isSuccessStatusCode) { + throw com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException( + message = response.statusMessage + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt new file mode 100644 index 00000000..de83f9fc --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt @@ -0,0 +1,73 @@ +package com.expediagroup.sdk.v2.core.request.initializer + +import com.expediagroup.sdk.v2.core.model.UserAgent +import com.google.api.client.googleapis.services.AbstractGoogleClientRequest +import com.google.api.client.googleapis.services.CommonGoogleClientRequestInitializer + +/** + * Initializes API client requests with default settings. + * + * This class extends `CommonGoogleClientRequestInitializer` and provides + * a mechanism for setting up default initialization parameters for API client requests. + * + * @constructor Creates an instance of `DefaultApiClientRequestInitializer` with a provided builder. + * + * @param builder The builder used to construct the `DefaultApiClientRequestInitializer` instance. + */ +class ApiClientRequestInitializer( + builder: Builder +) : CommonGoogleClientRequestInitializer(builder) { + companion object { + /** + * Provides a default instance of `DefaultApiClientRequestInitializer` using the internal builder. + * + * This method creates a new instance of `Builder` and uses it to construct and return a + * `DefaultApiClientRequestInitializer` object. It serves as the standard way to obtain + * a pre-configured request initializer for API client requests. + * + * @return A default instance of `DefaultApiClientRequestInitializer` configured with the default builder settings. + */ + @JvmStatic + fun default(): ApiClientRequestInitializer { + val builder = Builder() + return ApiClientRequestInitializer(builder) + } + + /** + * A builder class for creating instances of `DefaultApiClientRequestInitializer`. + * + * This class extends `CommonGoogleClientRequestInitializer.Builder` and overrides the `build` method + * to return a `DefaultApiClientRequestInitializer` with the current builder instance as a parameter. + */ + class Builder : CommonGoogleClientRequestInitializer.Builder() { + override fun build(): ApiClientRequestInitializer { + return ApiClientRequestInitializer(this) + } + } + } + + /** + * Initializes the provided Google client request. + * + *@param request The Google client request to be initialized. + */ + override fun initialize(request: AbstractGoogleClientRequest<*>) { + super.initialize(request) + overrideUserAgent(request) + } + + /** + * Overrides the user agent in the provided Google client request with a custom user agent string. + * + * @param request The Google client request whose user agent will be overridden. + */ + private fun overrideUserAgent(request: AbstractGoogleClientRequest<*>) { + val (jdk, _, os) = request.requestHeaders.getFirstHeaderStringValue("x-goog-api-client") + .split(" ") + + request.requestHeaders.userAgent = UserAgent( + jdkVersion = jdk, + operatingSystem = os + ).toString() + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt new file mode 100644 index 00000000..39a33fb5 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt @@ -0,0 +1,20 @@ +package com.expediagroup.sdk.v2.core.request.initializer + +import com.expediagroup.sdk.v2.core.request.interceptor.HttpRequestLoggingInterceptor +import com.expediagroup.sdk.v2.core.request.interceptor.HttpResponseLoggingInterceptor +import com.google.api.client.http.HttpRequest + +object InitializerFunctions { + val disableInternalLogging = fun (request: HttpRequest) { + request.isCurlLoggingEnabled = false + request.isLoggingEnabled = false + } + + val attachDefaultRequestLoggingInterceptor = fun (request: HttpRequest) { + request.interceptor = HttpRequestLoggingInterceptor() + } + + val attachDefaultResponseLoggingInterceptor = fun (request: HttpRequest) { + request.responseInterceptor = HttpResponseLoggingInterceptor() + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt new file mode 100644 index 00000000..d66c2e43 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt @@ -0,0 +1,28 @@ +package com.expediagroup.sdk.v2.core.request.initializer + +import com.google.api.client.http.HttpRequest +import com.google.api.client.http.HttpRequestInitializer + +class SdkRequestInitializer( + vararg functions: (HttpRequest) -> Unit +) : HttpRequestInitializer { + private val functions: Array<(HttpRequest) -> Unit> = arrayOf(*functions) + + companion object { + fun default(): SdkRequestInitializer = SdkRequestInitializer( + InitializerFunctions.attachDefaultRequestLoggingInterceptor, + InitializerFunctions.attachDefaultResponseLoggingInterceptor, + InitializerFunctions.disableInternalLogging, + ) + } + + override fun initialize(request: HttpRequest) { + functions.forEach { function -> function(request) } + } + + fun add(vararg initializers: HttpRequestInitializer): SdkRequestInitializer = + SdkRequestInitializer(*functions, *initializers.map { it::initialize }.toTypedArray()) + + fun add(vararg initializers: SdkRequestInitializer): SdkRequestInitializer = + SdkRequestInitializer(*functions, *initializers.map { it::initialize }.toTypedArray()) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt new file mode 100644 index 00000000..0c94ca4e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt @@ -0,0 +1,125 @@ +package com.expediagroup.sdk.v2.core.request.interceptor + +import com.expediagroup.sdk.v2.core.constant.LoggingMessage.OMITTED +import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLogger +import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.v2.core.logging.LogMessageConstant +import com.expediagroup.sdk.v2.core.logging.LogMessageTag +import com.expediagroup.sdk.v2.core.logging.LOGGABLE_CONTENT_TYPES +import com.expediagroup.sdk.v2.core.logging.mask.isMaskedField +import com.google.api.client.http.HttpExecuteInterceptor +import com.google.api.client.http.HttpRequest +import com.google.api.client.http.InputStreamContent +import okio.Buffer +import okio.IOException + +/** + * HttpRequestLoggingInterceptor is an implementation of HttpExecuteInterceptor that logs HTTP request details, + * such as headers and body content, for outgoing requests. + */ +class HttpRequestLoggingInterceptor : HttpExecuteInterceptor { + private val logger: ExpediaGroupLogger = + ExpediaGroupLoggerFactory.getLogger(HttpRequestLoggingInterceptor::class.java) + + /** + * Logs the HTTP request method, URL, and headers. + * Headers that are deemed sensitive by the `isMaskedField` function are masked. + * + * @param request The HTTP request object containing the details to be logged. + */ + private fun logRequestEventAndHeaders(request: HttpRequest) { + StringBuilder().apply { + appendLine("${request.requestMethod} ${request.url.clone().build()}") + appendLine(LogMessageConstant.REQUEST_HEADERS) + + request.headers.forEach { (key, value) -> + val keyCases = listOf( + key, + key.capitalize(), + key.uppercase(), + key.lowercase(), + ) + + appendLine("${key}: ${if (keyCases.any(::isMaskedField)) OMITTED else value}") + } + + logger.info(this.toString(), LogMessageTag.OUTGOING) + } + } + + /** + * Logs the body of an HTTP request if it meets certain criteria. + * + * If the request body is empty, it skips logging the body. + * If the body content type is not supported for logging, it logs a message indicating this. + * Otherwise, it logs the content of the request. + * + * @param request The HTTP request object containing the body to be logged. + * @throws IOException if an I/O error occurs while reading the request body. + */ + @Throws(IOException::class) + private fun logRequestBody(request: HttpRequest) { + StringBuilder().apply { + appendLine(LogMessageConstant.REQUEST_BODY) + + if (request.content.length == 0L) { + return + } + + if (!canLogBody(request)) { + appendLine(LogMessageConstant.BODY_CONTENT_TYPE_NOT_SUPPORTED.format(request.content.type)) + logger.debug(this.toString(), LogMessageTag.OUTGOING) + return + } + + appendLine(readAndResetContent(request)) + + logger.debug(this.toString(), LogMessageTag.OUTGOING) + } + } + + /** + * Determines if the body of an HTTP request can be logged. + * + * @param request The HTTP request to check. + * @return `true` if the body of the HTTP request can be logged, `false` otherwise. + */ + private fun canLogBody(request: HttpRequest): Boolean { + val hasContent = request.content.length != 0L + + val contentType = request.content.type.split(";").firstOrNull() + val isLoggableContentType = contentType in LOGGABLE_CONTENT_TYPES + + return hasContent.and(isLoggableContentType) + } + + /** + * Reads the content of the provided HTTP request's body and returns it as a string. + * Resets the content of the request to allow it to be read again. + * + * @param request The HTTP request whose content will be read and reset. + * @return The content of the HTTP request as a string. + * @throws IOException If an I/O error occurs while reading the request body. + */ + @Throws(IOException::class) + private fun readAndResetContent(request: HttpRequest): String = Buffer().apply { + request.content.writeTo(outputStream()) + request.content = InputStreamContent(request.content.type, clone().inputStream()) + }.readUtf8() + + /** + * Intercepts an HTTP request to log its details. + * + * This method logs both the headers and the body of the HTTP request. + * It first logs the request method, URL, and headers by calling `logRequestEventAndHeaders`. + * Next, it logs the body of the request by calling `logRequestBody`. + * + * @param request The HTTP request object containing the details to be intercepted and logged. + * @throws IOException if an I/O error occurs while reading the request body. + */ + @Throws(IOException::class) + override fun intercept(request: HttpRequest) { + logRequestEventAndHeaders(request) + logRequestBody(request) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt new file mode 100644 index 00000000..5a55409d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt @@ -0,0 +1,126 @@ +package com.expediagroup.sdk.v2.core.request.interceptor + +import com.expediagroup.sdk.v2.core.constant.LoggingMessage.OMITTED +import com.expediagroup.sdk.v2.core.logging.LogMessageConstant +import com.expediagroup.sdk.v2.core.logging.LOGGABLE_CONTENT_TYPES +import com.expediagroup.sdk.v2.core.logging.LogMessageTag +import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLogger +import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.v2.core.logging.mask.isMaskedField +import com.google.api.client.http.HttpResponse +import com.google.api.client.http.HttpResponseInterceptor +import okio.IOException +import okio.buffer +import okio.source + + +/** + * The HttpResponseLoggingInterceptor class is responsible for intercepting HTTP responses and logging + * relevant information such as headers, status, and body content. + */ +class HttpResponseLoggingInterceptor : HttpResponseInterceptor { + private val logger: ExpediaGroupLogger = + ExpediaGroupLoggerFactory.getLogger(HttpResponseLoggingInterceptor::class.java) + + /** + * Logs the HTTP response event and its headers. This function constructs a log message + * containing the HTTP method, URL, status code, and response headers, and then logs it. + * + * @param response The HTTP response object from which the information is extracted. + */ + private fun logResponseEventAndHeaders(response: HttpResponse) { + StringBuilder().apply { + val request = response.request + + val method = request.requestMethod + val url = request.url.clone().build() + val status = "[${response.statusCode} ${response.statusMessage}]" + + appendLine("Response from: $method $url $status") + appendLine(LogMessageConstant.RESPONSE_HEADERS) + + response.headers.forEach { (key, value) -> + val keyCases = listOf( + key, + key.capitalize(), + key.uppercase(), + key.lowercase(), + ) + + appendLine("${key}: ${if (keyCases.any(::isMaskedField)) OMITTED else value}") + } + + logger.info(this.toString(), LogMessageTag.INCOMING) + } + } + + /** + * Logs the body of an HTTP response if it is safe to log based on its content type and length. + * + * @param response The HTTP response object containing the body to be logged. + * @throws IOException If an I/O error occurs during reading the response content. + */ + @Throws(IOException::class) + private fun logResponseBody(response: HttpResponse) { + StringBuilder().apply stringBuilder@ { + if (setOf(null, 0).contains(response.headers.contentLength)) { + logger.info(LogMessageConstant.EMPTY_OR_UNKNOWN_RESPONSE_BODY, LogMessageTag.INCOMING) + return + } + + if (!canLogBody(response)) { + this@stringBuilder.appendLine(LogMessageConstant.BODY_CONTENT_TYPE_NOT_SUPPORTED) + logger.debug(this.toString(), LogMessageTag.INCOMING) + return + } + + val contentLength = response.headers.contentLength + val contentLengthExceedsThreshold = (contentLength > Int.MAX_VALUE.toLong()) + + this@stringBuilder.appendLine(LogMessageConstant.RESPONSE_BODY) + + if (!response.content.markSupported()) { + logger.error(LogMessageConstant.RESPONSE_CONTENT_INPUT_STREAM_DOES_NOT_SUPPORT_MARK, LogMessageTag.INCOMING) + return + } + + response.content.apply stream@ { + this@stream.mark(contentLength.toInt() + 1) + + this@stringBuilder.appendLine(response.content.source().buffer().readUtf8()) + if (contentLengthExceedsThreshold) { + this@stringBuilder.appendLine(LogMessageConstant.RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE) + } + + this@stream.reset() + } + + logger.info(this.toString(), LogMessageTag.INCOMING) + } + } + + /** + * Determines if the body of the HTTP response can be logged based on content length and content type. + * + * @param response The HTTP response to check. + * @return `true` if the response body can be logged, `false` otherwise. + */ + private fun canLogBody(response: HttpResponse): Boolean { + val hasContent = response.headers.contentLength > 0L + val isLoggableContentType = response.headers.contentType in LOGGABLE_CONTENT_TYPES + + return hasContent.and(isLoggableContentType) + } + + /** + * Intercepts the given HTTP response to log its event, headers, and body. + * + * @param response The HTTP response to be intercepted and logged. + * @throws IOException If an I/O error occurs during logging the response content. + */ + @Throws(IOException::class) + override fun interceptResponse(response: HttpResponse) { + logResponseEventAndHeaders(response) + logResponseBody(response) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt new file mode 100644 index 00000000..071670e2 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt @@ -0,0 +1,7 @@ +package com.expediagroup.sdk.v2.core.trait + +/** + * Marker interface for defining traits in a class. Traits serve as a means to define common + * behaviors or attributes that can be shared across different implementations. + */ +interface Trait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt new file mode 100644 index 00000000..c1d8a51b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt @@ -0,0 +1,19 @@ +package com.expediagroup.sdk.v2.core.trait.authentication + +import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.google.api.client.http.HttpTransport + +/** + * Interface representing a trait for handling authentication. + * + * Implementing classes are responsible for creating an instance + * of the `RefreshAccessTokenTrait` using given client configuration + * and HTTP transport. + */ +interface AuthenticationHandlerTrait : Trait { + fun createAuthenticationHandler( + config: ClientConfiguration, + transport: HttpTransport, + ): RefreshAccessTokenTrait +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt new file mode 100644 index 00000000..4c480dc5 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.authentication + +import com.expediagroup.sdk.v2.core.trait.Trait +import com.google.auth.oauth2.AccessToken + +/** + * Interface representing a trait for refreshing access tokens. + * + * Implementing classes are responsible for providing a method to refresh + * access tokens, which can be used for authentication and authorization + * in client-server communications. + */ +interface RefreshAccessTokenTrait : Trait { + fun refreshAccessToken(): AccessToken? +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt new file mode 100644 index 00000000..aeccafd5 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt @@ -0,0 +1,12 @@ +package com.expediagroup.sdk.v2.core.trait.common + +import com.expediagroup.sdk.v2.core.trait.Trait + +/** + * Defines a trait for building instances of a specific type. + * + * @param BuiltType The type of object that this builder will create. + */ +interface BuilderTrait: Trait { + fun build(): BuiltType +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt new file mode 100644 index 00000000..71b1886c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.common + +import com.expediagroup.sdk.v2.core.trait.Trait + + +/** + * Interface for defining configuration traits. + * + * Classes implementing this interface signify that they possess configurable attributes + * or behaviors which can be shared or reused across various implementations. + * + * This interface extends the `Trait` interface, indicating that it can be used as a marker + * to define common functionalities or properties in a polymorphic way. + */ +interface ConfigurationTrait: Trait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt new file mode 100644 index 00000000..0f2ec87c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.v2.core.trait.common + +import com.expediagroup.sdk.v2.core.trait.Trait +import java.util.UUID + +/** + * Interface representing a trait for unique identifier management in a class. + * + * Classes implementing this trait inherit the behavior of generating and handling + * a universally unique identifier (UUID). The `id` property provides a generated UUID + * that uniquely identifies an instance. + */ +interface IdTrait: Trait { + val id: UUID + get() = UUID.randomUUID() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt new file mode 100644 index 00000000..20912259 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt @@ -0,0 +1,12 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface representing a trait that provides the authentication endpoint. + * + * This trait extends `ClientConfiguration` and includes a method to retrieve + * the authentication endpoint URL. Classes implementing this trait should + * provide the necessary logic to fetch or compute the authentication endpoint. + */ +interface AuthEndpointTrait: ClientConfiguration { + fun getAuthEndpoint(): String +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt new file mode 100644 index 00000000..4a868a30 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt @@ -0,0 +1,17 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy + +/** + * Interface representing a trait for authentication strategies in client configurations. + * + * This trait extends the `ClientConfiguration` interface, thereby inheriting + * its configuration management features and unique identifier characteristics. + * + * Classes implementing this trait should provide the specific implementation + * for retrieving an `AuthenticationStrategy` which dictates how the client + * will handle authentication. + */ +interface AuthenticationStrategyTrait : ClientConfiguration { + fun getAuthenticationStrategy(): AuthenticationStrategy +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt new file mode 100644 index 00000000..ed6e52bd --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +import com.expediagroup.sdk.v2.core.trait.common.ConfigurationTrait +import com.expediagroup.sdk.v2.core.trait.common.IdTrait + +/** + * Interface that combines configuration traits with an identifier. + * + * This interface extends from `ConfigurationTrait` and `IdTrait`, thereby inheriting the + * behavior of configuration management and uniquely identifying the configured client-instances + * with a universally unique identifier (UUID). + * + * Implementing classes are expected to manage lower-level client configurations and may + * utilize the inherited `id` property to differentiate between instances. + */ +interface ClientConfiguration: ConfigurationTrait, IdTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt new file mode 100644 index 00000000..969547b2 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt @@ -0,0 +1,10 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +import com.expediagroup.sdk.v2.core.trait.common.ConfigurationTrait + +/** + * Interface representing client-specific configuration traits. This interface inherits + * from `ConfigurationTrait` and is intended to define configurations common to client + * implementations. + */ +interface ClientConfigurationTrait: ConfigurationTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt new file mode 100644 index 00000000..6953d8d4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt @@ -0,0 +1,13 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface for configuration traits that require specifying the connection timeout duration. + * + * Classes implementing this trait should provide a method to retrieve the connection timeout value + * in milliseconds. The connection timeout determines the maximum time to wait while establishing a connection. + * + * This trait extends `ClientConfiguration`, which is responsible for handling general client configuration settings. + */ +interface ConnectionTimeoutTrait: ClientConfiguration { + fun getConnectionTimeout(): Long +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt new file mode 100644 index 00000000..f26a5086 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt @@ -0,0 +1,11 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface that represents an endpoint trait in a client configuration. + * + * This trait is responsible for providing the endpoint URL which + * is essential for making network requests. + */ +interface EndpointTrait: ClientConfiguration { + fun getEndpoint(): String +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt new file mode 100644 index 00000000..7bfe96ea --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt @@ -0,0 +1,33 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface representing a full configuration trait which amalgamates + * various individual configuration traits required for client configuration. + * + * This interface extends the following traits: + * - `KeyTrait`: Provides access to the client's key. + * - `SecretTrait`: Provides access to the client's secret. + * - `EndpointTrait`: Provides access to the endpoint. + * - `AuthEndpointTrait`: Provides access to the authentication endpoint. + * - `MaskedLoggingHeadersTrait`: Provides headers that should be masked in logs. + * - `MaskedLoggingBodyFieldsTrait`: Provides body fields that should be masked in logs. + * - `RequestTimeoutTrait`: Provides the request timeout duration. + * - `SocketTimeoutTrait`: Provides the socket timeout duration. + * - `ConnectionTimeoutTrait`: Provides the connection timeout duration. + * - `AuthenticationStrategyTrait`: Provides the authentication strategy to be used. + * - `MaxConnectionsTotalTrait`: Provides the maximum number of total connections allowed. + * - `MaxConnectionsPerRouteTrait`: Provides the maximum number of connections allowed per route. + */ +interface FullConfigurationTrait : + KeyTrait, + SecretTrait, + EndpointTrait, + AuthEndpointTrait, + MaskedLoggingHeadersTrait, + MaskedLoggingBodyFieldsTrait, + RequestTimeoutTrait, + SocketTimeoutTrait, + ConnectionTimeoutTrait, + AuthenticationStrategyTrait, + MaxConnectionsTotalTrait, + MaxConnectionsPerRouteTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt new file mode 100644 index 00000000..09ec2c93 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * KeyTrait interface for client configurations that provides access to the client's key. + * + * This interface extends ClientConfiguration, thereby inheriting configuration management and + * unique client-instance identification behavior. Classes implementing this interface are expected + * to provide a method to retrieve the client's key for various operations such as authentication. + * + * Functions: + * - getKey: Retrieves the client's key as a String. + */ +interface KeyTrait: ClientConfiguration { + fun getKey(): String +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt new file mode 100644 index 00000000..119ee5c6 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt @@ -0,0 +1,14 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Trait for managing the body fields that should be masked in logs. + * + * This trait extends `ClientConfiguration`, adding a specific method that provides + * a set of body field names whose contents should not appear in logs. + * + * The `getMaskedLoggingBodyFields` method is used to define which fields in the body of + * requests and responses should be masked for security and privacy reasons. + */ +interface MaskedLoggingBodyFieldsTrait: ClientConfiguration { + fun getMaskedLoggingBodyFields(): Set +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt new file mode 100644 index 00000000..5c5a49e1 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt @@ -0,0 +1,14 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Trait for managing the headers that should be masked in logs. + * + * This trait extends `ClientConfiguration`, adding a specific method to provide + * a set of HTTP header names whose values should not appear in logs. + * + * The `getMaskedLoggingHeaders` method allows specifying headers that need + * to be masked for security and privacy reasons. + */ +interface MaskedLoggingHeadersTrait: ClientConfiguration { + fun getMaskedLoggingHeaders(): Set +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt new file mode 100644 index 00000000..88b51bcb --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface for configuring the maximum number of connections allowed per route. + * + * This interface extends `ClientConfiguration`, inheriting the trait behavior + * necessary for managing client configurations. Classes implementing this + * interface must provide an implementation for retrieving the maximum number + * of connections allowed per route. + * + * Functions: + * - getMaxConnectionsPerRoute: Retrieves the maximum number of connections allowed per route. + */ +interface MaxConnectionsPerRouteTrait: ClientConfiguration { + fun getMaxConnectionsPerRoute(): Int +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt new file mode 100644 index 00000000..5f34b911 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface defining a trait for configuring the maximum number of total connections. + * + * This interface extends `ClientConfiguration`, thereby incorporating configuration management + * and unique client-instance identification features. Implementing classes should provide logic + * to retrieve the maximum number of total connections allowed. + * + * Functions: + * - getMaxConnectionsTotal: Retrieves the maximum number of total connections allowed. + */ +interface MaxConnectionsTotalTrait: ClientConfiguration { + fun getMaxConnectionsTotal(): Int +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt new file mode 100644 index 00000000..cf4d55b4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface for managing the request timeout configuration. + * + * This trait extends the ClientConfiguration interface, inheriting properties related + * to overall client configuration management and unique client-instance identification. + * Implementing classes should provide a method to retrieve the request timeout duration in milliseconds. + * + * Functions: + * - getRequestTimeout: Retrieves the request timeout duration. + */ +interface RequestTimeoutTrait: ClientConfiguration { + fun getRequestTimeout(): Long +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt new file mode 100644 index 00000000..3c9372cb --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Interface representing a configuration trait that provides access to a secret. + * + * This interface extends `ClientConfiguration`, inheriting the properties and behaviors of + * configuration management and unique client-instance identification. Classes implementing + * `SecretTrait` are expected to provide a method to retrieve a secret value, often used + * in authentication or other secure operations. + * + * Functions: + * - getSecret: Retrieves the client's secret as a String. + */ +interface SecretTrait: ClientConfiguration { + fun getSecret(): String +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt new file mode 100644 index 00000000..751add8b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt @@ -0,0 +1,15 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +/** + * Trait representing the socket timeout configuration for a client. + * + * This interface extends `ClientConfiguration`, incorporating the behavior of configuration management + * and unique client-instance identification. Classes implementing `SocketTimeoutTrait` are expected + * to provide a method to retrieve the socket timeout duration in milliseconds. + * + * Functions: + * - getSocketTimeout: Retrieves the socket timeout duration in milliseconds. + */ +interface SocketTimeoutTrait: ClientConfiguration { + fun getSocketTimeout(): Long +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt new file mode 100644 index 00000000..a185de00 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt @@ -0,0 +1,13 @@ +package com.expediagroup.sdk.v2.core.trait.configuration + +import com.expediagroup.sdk.v2.core.trait.Trait + +/** + * An interface representing a trait that can be converted to a `ClientConfigurationTrait`. + * It provides methods to convert with or without default configurations. + */ +interface ToClientConfigurationTrait: Trait { + fun toClientConfiguration(withDefault: ClientConfigurationTrait): ClientConfigurationTrait + + fun toClientConfiguration(): ClientConfigurationTrait +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt new file mode 100644 index 00000000..67006091 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -0,0 +1,195 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.configuration + +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException +import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.v2.core.configuration.ExpediaGroupDefaultClientConfiguration +import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration + +/** + * A configuration class that holds the necessary credentials and settings for API clients. + * + * This class is used to configure SDK clients by providing essential + * details such as API keys, environment, timeouts, and logging settings. + * + * It also provides a fluent `Builder` pattern for easy creation of configuration instances. + * + * @property key The API key used for authentication. + * @property secret The API secret used for authentication. + * @property environment The environment in which the API client will operate (e.g., production or test). + * @property requestTimeout The request timeout duration in milliseconds (optional). + * @property connectionTimeout The connection timeout duration in milliseconds (optional). + * @property socketTimeout The socket timeout duration in milliseconds (optional). + * @property maskedLoggingHeaders A set of HTTP headers whose values should be masked in logs (optional). + * @property maskedLoggingBodyFields A set of fields in the request body whose values should be masked in logs (optional). + */ +data class ClientConfiguration( + val key: String?, + val secret: String?, + val environment: ClientEnvironment?, + val requestTimeout: Long? = null, + val connectionTimeout: Long? = null, + val socketTimeout: Long? = null, + val maskedLoggingHeaders: Set? = null, + val maskedLoggingBodyFields: Set? = null, + val maxConnTotal: Int? = null, + val maxConnPerRoute: Int? = null +) { + + /** + * A builder for creating `ClientConfiguration` instances. + */ + class Builder { + private var key: String? = null + private var secret: String? = null + private var environment: ClientEnvironment? = null + private var requestTimeout: Long? = null + private var connectionTimeout: Long? = null + private var socketTimeout: Long? = null + private var maskedLoggingHeaders: Set? = null + private var maskedLoggingBodyFields: Set? = null + private var maxConnTotal: Int? = null + private var maxConnPerRoute: Int? = null + + /** + * Sets the API key. + * @param key The API key to use. + */ + fun key(key: String) = apply { + this.key = key + } + + /** + * Sets the API secret. + * @param secret The API secret to use. + */ + fun secret(secret: String) = apply { + this.secret = secret + } + + /** + * Sets the environment (e.g., production, test, or sandbox). + * @param environment The `ClientEnvironment` to use. + */ + fun environment(environment: ClientEnvironment) = apply { + this.environment = environment + } + + /** + * Sets the request timeout in milliseconds. + * @param requestTimeout The request timeout duration. + */ + fun requestTimeout(requestTimeout: Long) = apply { + this.requestTimeout = requestTimeout + } + + /** + * Sets the connection timeout in milliseconds. + * @param connectionTimeout The connection timeout duration. + */ + fun connectionTimeout(connectionTimeout: Long) = apply { + this.connectionTimeout = connectionTimeout + } + + /** + * Sets the socket timeout in milliseconds. + * @param socketTimeout The socket timeout duration. + */ + fun socketTimeout(socketTimeout: Long) = apply { + this.socketTimeout = socketTimeout + } + + /** + * Sets the headers whose values should be masked in logs. + * @param maskedLoggingHeaders A set of HTTP headers to mask in logs. + */ + fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { + this.maskedLoggingHeaders = maskedLoggingHeaders + } + + /** + * Sets the body fields whose values should be masked in logs. + * @param maskedLoggingBodyFields A set of fields in the request body to mask in logs. + */ + fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { + this.maskedLoggingBodyFields = maskedLoggingBodyFields + } + + fun maxConnTotal(maxConnTotal: Int) = apply { + this.maxConnTotal = maxConnTotal + } + + fun maxConnPerRoute(maxConnPerRoute: Int) = apply { + this.maxConnPerRoute = maxConnPerRoute + } + + /** + * Builds and returns the `ClientConfiguration` instance. + * @return The configured `ClientConfiguration`. + */ + fun build(): ClientConfiguration { + return ClientConfiguration( + key, + secret, + environment, + requestTimeout, + connectionTimeout, + socketTimeout, + maskedLoggingHeaders, + maskedLoggingBodyFields, + maxConnTotal, + maxConnPerRoute, + ) + } + } + + companion object { + @JvmStatic + fun builder(): Builder = Builder() + } + + internal fun toFullClientConfiguration( + endpointProvider: (ClientEnvironment) -> String, + authEndpointProvider: (ClientEnvironment) -> String, + defaultEnvironment: ClientEnvironment = ClientEnvironment.PROD + ): FullClientConfiguration { + val environment = this.environment ?: defaultEnvironment + + return object : FullClientConfiguration { + override fun getKey(): String = + key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") + + override fun getSecret(): String = + secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") + + override fun getEndpoint(): String = + endpointProvider(environment) + + override fun getAuthEndpoint(): String = + authEndpointProvider(environment) + + override fun getMaskedLoggingHeaders(): Set = + maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() + + override fun getMaskedLoggingBodyFields(): Set = + maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() + + override fun getRequestTimeout(): Long = + requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() + + override fun getSocketTimeout(): Long = + socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() + + override fun getConnectionTimeout(): Long = + connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() + + override fun getAuthenticationStrategy(): AuthenticationStrategy = + ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() + + override fun getMaxConnectionsTotal(): Int = + maxConnTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() + + override fun getMaxConnectionsPerRoute(): Int = + maxConnPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt new file mode 100644 index 00000000..3513b096 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt @@ -0,0 +1,38 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.configuration + +/** + * An enumeration representing the possible environments for the API clients. + * + * This enum defines the different environments in which an API client can operate, including + * production, test, and sandbox environments. It is used in the configuration to determine + * which endpoints and settings to apply based on the environment selected. + */ +enum class ClientEnvironment { + + /** + * Represents the production environment. + * Clients in this environment interact with the live, production-grade API. + */ + PROD, + + /** + * Represents the test environment. + * Clients in this environment interact with EG internal test/lab API. + */ + TEST, + + /** + * Represents the sandbox version of the production environment. + * + * This environment is only available for `SupplyClient` + */ + SANDBOX_PROD, + + /** + * Represents the sandbox version of the EG internal test/lab environment. + * + * This environment is only available for `SupplyClient` + */ + SANDBOX_TEST +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt new file mode 100644 index 00000000..ab259313 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt @@ -0,0 +1,103 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.configuration + +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException + +enum class SupplyClientEndpoint(val url: String) { + PROD("https://api.expediagroup.com/supply/lodging/graphql"), + TEST("https://test-api.expediagroup.com/supply/lodging/graphql"), + SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging/graphql"), + SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging/graphql") +} + +enum class PaymentClientEndpoint(val url: String) { + PROD("https://api.expediagroup.com/supply/payments/graphql"), + TEST("https://test-api.expediagroup.com/supply/payments/graphql") +} + +enum class SandboxDataManagementClientEndpoint(val url: String) { + SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql"), + SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql") +} + +enum class FileManagementClientEndpoint(val url: String) { + PROD("https://api.expediagroup.com/supply-lodging/v1/files"), + TEST("https://test-api.expediagroup.com/supply-lodging/v1/files") +} + +enum class AuthEndpoint(val url: String) { + PROD("https://api.expediagroup.com/identity/oauth2/v3/token/"), + TEST("https://test-api.expediagroup.com/identity/oauth2/v3/token/"), + SANDBOX_PROD("https://api.expediagroup.com/identity/oauth2/v3/token/"), + SANDBOX_TEST("https://test-api.expediagroup.com/identity/oauth2/v3/token/") +} + +/** + * An internal utility object for providing API endpoints based on the environment. + * + * This class contains methods to retrieve the correct endpoint for different clients + * (e.g., Supply, Payment, Sandbox, File Management, and Authentication) based on the + * provided `ClientEnvironment`. + * + * If an unsupported environment is passed, an `IllegalArgumentException` is thrown. + */ +object EndpointProvider { + fun getSupplyClientEndpoint(environment: ClientEnvironment): String { + return try { + SupplyClientEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw ExpediaGroupConfigurationException( + """ + Unsupported environment [$environment] for SupplyClient. + Supported environments are [${SupplyClientEndpoint.entries.joinToString(", ") }] + """ + ) + } + } + + fun getPaymentClientEndpoint(environment: ClientEnvironment): String { + return try { + PaymentClientEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw ExpediaGroupConfigurationException( + """ + Unsupported environment [$environment] for PaymentClient. + Supported environments are [${PaymentClientEndpoint.entries.joinToString(", ") }] + """ + ) + } + } + + fun getSandboxDataManagementClientEndpoint(environment: ClientEnvironment): String { + return try { + SandboxDataManagementClientEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw ExpediaGroupConfigurationException( + """ + Unsupported environment [$environment] for SandboxDataManagementClient. + Supported environments are [${SandboxDataManagementClientEndpoint.entries.joinToString(", ") }] + """ + ) + } + } + + fun getFileManagementClientEndpoint(environment: ClientEnvironment): String { + return try { + FileManagementClientEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("Unsupported environment [$environment] for FileManagementClient") + } + } + + fun getAuthEndpoint(environment: ClientEnvironment): String { + return try { + AuthEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw ExpediaGroupConfigurationException( + """ + Unsupported environment [$environment] for Authentication. + Supported environments are [${AuthEndpoint.entries.joinToString(", ") }] + """ + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt new file mode 100644 index 00000000..757d268a --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt @@ -0,0 +1,133 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client + +import com.expediagroup.sdk.v2.core.client.SdkClient +import com.expediagroup.sdk.v2.core.configuration.DefaultClientBuilder +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.FileUploadRequest +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.Upload201Response +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileDownloadOperation +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileDownloadOperationParams +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileUploadOperation +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileUploadOperationParams +import java.io.* + +class FileManagementClient( + configuration: ClientConfiguration +) { + private val client = SdkClient( + configuration = configuration.toFullClientConfiguration( + endpointProvider = EndpointProvider::getFileManagementClientEndpoint, + authEndpointProvider = EndpointProvider::getAuthEndpoint + ) + ) + + @Throws(IOException::class) + private fun buildOperation( + id: String, + type: String? = null, + value: String? = null + ) = FileDownloadOperation( + params = FileDownloadOperationParams( + id = id, + type = type, + value = value + ) + ) + + + @Throws(IOException::class) + @JvmOverloads + fun download( + id: String, + downloadTo: OutputStream, + type: String? = null, + value: String? = null + ) = + buildOperation( + id = id, + type = type, + value = value + ).let { + client.executeAndDownloadTo(it, downloadTo) + } + + @Throws(IOException::class) + @JvmOverloads + fun download( + id: String, + type: String? = null, + value: String? = null + ): InputStream? { + buildOperation( + id = id, + type = type, + value = value + ).let { + return client.executeAsInputStream(it) + } + } + + @Throws(IOException::class) + @JvmOverloads + fun upload( + file: File, + type: String? = null, + value: String? = null, + ): Upload201Response { + val params = FileUploadOperationParams( + type = type, + value = value + ) + + val body = FileUploadRequest(file) + + val operation = FileUploadOperation( + requestBody = body, + params = params, + ) + + return client.execute(operation, false) as Upload201Response + } + + @Throws(IOException::class) + @JvmOverloads + fun upload( + stream: InputStream, + type: String? = null, + value: String? = null, + fileContentType: String? = null, + fileExtension: String? = null + ): Upload201Response { + val params = FileUploadOperationParams( + type = type, + value = value + ) + + val body = FileUploadRequest(stream) + + val operation = FileUploadOperation( + requestBody = body, + params = params, + fileContentType = fileContentType, + fileExtension = fileExtension + ) + + return client.execute( + operation = operation, + enableGzipContent = false, + ) as Upload201Response + } + + companion object { + @JvmStatic + fun builder(): Builder = Builder() + + class Builder: DefaultClientBuilder() { + private val configurationBuilder = ClientConfiguration.builder() + + override fun build(): FileManagementClient = + FileManagementClient(configurationBuilder.build()) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt new file mode 100644 index 00000000..f8f41029 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt @@ -0,0 +1,12 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models + +import java.io.File +import java.io.InputStream + +class FileUploadRequest private constructor( + val content: Any? +) { + + constructor(file: File) : this(content = file) + constructor(inputStream: InputStream) : this(content = inputStream) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt new file mode 100644 index 00000000..22a800a1 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models + + +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import com.fasterxml.jackson.annotation.JsonProperty +import javax.validation.Valid + +/** + * The object used the describe an error, containing both human-readable and in a machine-readable information. + * @param type Snake cased all caps error code interpreted from the HTTP status code that can programmatically be acted upon. + * @param detail A human-readable explanation of the error, specific to this error occurrence. + */ +data class GenericError( + /* Snake cased all caps error code interpreted from the HTTP status code that can programmatically be acted upon. */ + @JsonProperty("type") + + + @field:Valid + val type: kotlin.String, + + /* A human-readable explanation of the error, specific to this error occurrence. */ + @JsonProperty("detail") + + + @field:Valid + val detail: kotlin.String +) { + + + companion object { + @JvmStatic + fun builder() = Builder() + } + + class Builder( + private var type: kotlin.String? = null, + private var detail: kotlin.String? = null + ) { + fun type(type: kotlin.String) = apply { this.type = type } + fun detail(detail: kotlin.String) = apply { this.detail = detail } + + fun build(): GenericError { + // Check required params + validateNullity() + return GenericError( + type = type!!, + detail = detail!! + ) + } + + private fun validateNullity() { + if (type == null) { + throw NullPointerException("Required parameter type is missing") + } + if (detail == null) { + throw NullPointerException("Required parameter detail is missing") + } + } + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt new file mode 100644 index 00000000..038aa226 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models + + +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import com.fasterxml.jackson.annotation.JsonProperty +import javax.validation.Valid + +/** + * + * @param id File identifier + */ +data class Upload201Response( + /* File identifier */ + @JsonProperty("id") + + + @field:Valid + val id: kotlin.String? = null +) { + + + companion object { + @JvmStatic + fun builder() = Builder() + } + + class Builder( + private var id: kotlin.String? = null + ) { + fun id(id: kotlin.String?) = apply { this.id = id } + + fun build(): Upload201Response { + return Upload201Response( + id = id + ) + } + + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt new file mode 100644 index 00000000..e2471771 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception + +import com.expediagroup.sdk.core.model.getTransactionId +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupApiException +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceDefaultErrorException +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.* +import io.ktor.client.call.* +import io.ktor.client.statement.* +import kotlinx.coroutines.runBlocking + +internal open class HttpStatusCodeRange( + private val statusCode: String, + val getException: (HttpResponse) -> ExpediaGroupApiException +) : Comparable { + open fun matches(statusCode: String): Boolean = if (isRangeDefinition()) this.statusCode.first() == statusCode.first() else this.statusCode == statusCode + open fun isRangeDefinition(): Boolean = statusCode.matches(Regex("^[1-5]XX$")) + override fun compareTo(other: HttpStatusCodeRange): Int = (if (this.isRangeDefinition()) 1 else 0).compareTo(if (other.isRangeDefinition()) 1 else 0) +} + +internal object DefaultHttpStatusCodeRange : HttpStatusCodeRange( + "DefaultHttpStatusCodeRange", + { ExpediaGroupServiceDefaultErrorException(it.status.value, runBlocking { it.bodyAsText() }, it.request.headers.getTransactionId()) } +) { + override fun matches(statusCode: String): Boolean = true + override fun isRangeDefinition(): Boolean = true +} + +internal object ErrorObjectMapper { + private val defaultHttpStatusCodeRanges = listOf(DefaultHttpStatusCodeRange) + private val httpStatusCodeRanges: Map< String, List< HttpStatusCodeRange > > = mapOf( + Pair( + "download", + listOf( + HttpStatusCodeRange("400") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("401") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("403") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("500") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("503") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + DefaultHttpStatusCodeRange + ) + ), + Pair( + "upload", + listOf( + HttpStatusCodeRange("400") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("401") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("403") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("500") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + HttpStatusCodeRange("503") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, + DefaultHttpStatusCodeRange + ) + ) + ) + + fun process(httpResponse: HttpResponse, operationId: String): ExpediaGroupApiException = + httpStatusCodeRanges.getOrDefault(operationId, defaultHttpStatusCodeRanges).filter { it.matches(httpResponse.status.value.toString()) }.min().getException(httpResponse) + + private inline fun fetchErrorObject(httpResponse: HttpResponse): T = runBlocking { + runCatching { httpResponse.body() }.getOrElse { throw ExpediaGroupServiceDefaultErrorException(httpResponse.status.value, httpResponse.bodyAsText(), httpResponse.request.headers.getTransactionId()) } + } +} + + class ExpediaGroupApiGenericErrorException(code: Int, override val errorObject: GenericError, transactionId: String?) : ExpediaGroupApiException(code, errorObject, transactionId) + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt new file mode 100644 index 00000000..56d5cb2a --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception + +/** + * An entity to represent a constraint violation of a property. + * + * @property name The name of the constraint-violated field + * @property path The path of the constraint-violated field + * @property message The constraint violation message + */ +data class PropertyConstraintViolation( + val name: String, + val path: String, + val message: String +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt new file mode 100644 index 00000000..04265239 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception + +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupClientException + +/** + * An exception to be thrown when a constraint on some property has been violated. + * + * @property message The detail message. + * @property constraintViolations A list of the constraint violations that occurred + */ +class PropertyConstraintViolationException( + message: String, + val constraintViolations: List +) : ExpediaGroupClientException("$message ${constraintViolations.joinToString(separator = ",\n\t- ", prefix = "[\n\t- ", postfix = "\n]")}") diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt new file mode 100644 index 00000000..314b7326 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt @@ -0,0 +1,32 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations + +import com.expediagroup.sdk.core.model.Nothing +import com.expediagroup.sdk.v2.core.model.Operation +import com.expediagroup.sdk.v2.core.model.OperationParams + +class FileDownloadOperation( + params: FileDownloadOperationParams, +) : Operation( + url = "/supply-lodging/v1/files/${params.id}/content", + method = "GET", + operationId = "downloadFile", + requestBody = null, + params = params, +) + +class FileDownloadOperationParams( + val id: String, + val type: String?, + val value: String?, +) : OperationParams { + override fun getHeaders(): Map = emptyMap() + + override fun getQueryParams(): Map> = buildMap { + if (!type.isNullOrBlank()) put("type", listOf(type)) + if (!value.isNullOrBlank()) put("value", listOf(value)) + } + + override fun getPathParams(): Map = buildMap { + put("id", id) + } +} \ No newline at end of file diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt new file mode 100644 index 00000000..41340b76 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt @@ -0,0 +1,92 @@ +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations + +import com.expediagroup.sdk.v2.core.http.BlobTypeDetector +import com.expediagroup.sdk.v2.core.model.OperationParams +import com.expediagroup.sdk.v2.core.model.Operation +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.FileUploadRequest +import com.google.api.client.http.* +import com.google.api.client.http.MultipartContent.Part +import org.apache.tika.mime.MimeTypes +import java.io.File +import java.io.InputStream +import java.util.* + + +class FileUploadOperation( + requestBody: FileUploadRequest, + params: FileUploadOperationParams, + private var fileContentType: String? = null, + private var fileExtension: String? = null, +) : Operation( + url = "/supply-lodging/v1/files", + method = "POST", + operationId = "uploadFile", + requestBody = requestBody, + params = params, + isUpload = true, +) { + init { + if (fileContentType == null) { + when (requestBody.content) { + is File -> fileContentType = BlobTypeDetector.getInstance().detect(requestBody.content) + is InputStream -> fileContentType = BlobTypeDetector.getInstance().detect(requestBody.content) + else -> throw IllegalArgumentException("Unsupported content type") + } + } + + if (fileExtension == null) { + when (requestBody.content) { + is File -> fileExtension = requestBody.content.extension + is InputStream -> fileExtension = MimeTypes.getDefaultMimeTypes().forName(fileContentType).extension + else -> throw IllegalArgumentException("Unsupported content type") + } + } + } + + override fun getHttpContent(): HttpContent { + return MultipartContent().apply { + // Set the overall media type for the multipart content + setMediaType(HttpMediaType("multipart/form-data").apply { + setBoundary("__END_OF_PART__${UUID.randomUUID()}") + }) + + val fileName = when (requestBody?.content) { + is File -> requestBody.content.name + is InputStream -> "file.$fileExtension" + else -> throw IllegalArgumentException("Unsupported content type") + } + + // Add the file part + addPart(Part().apply { + headers = HttpHeaders().apply { + setContentType("application/octet-stream") + set("Content-Disposition", "form-data; name=\"content\"; filename=\"$fileName\"") + } + + content = when (requestBody.content) { + is File -> FileContent(fileContentType, requestBody.content.absoluteFile) + is InputStream -> InputStreamContent(fileContentType, requestBody.content) + else -> throw IllegalArgumentException("Unsupported content type") + }.apply { + setBoundary("__END_OF_PART__${UUID.randomUUID()}") + } + }) + } + } +} + +class FileUploadOperationParams( + val type: String? = null, + val value: String? = null, +) : OperationParams { + override fun getHeaders(): Map = emptyMap() + + override fun getQueryParams(): Map> = + buildMap { + if (!type.isNullOrBlank()) put("type", listOf(type)) + if (!value.isNullOrBlank()) put("value", listOf(value)) + } + + override fun getPathParams(): Map = + emptyMap() +} \ No newline at end of file diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt new file mode 100644 index 00000000..da1fca65 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.validation + +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolation +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolationException +import javax.validation.ConstraintViolation +import javax.validation.Validation +import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator +import java.util.stream.Collectors + +internal object PropertyConstraintsValidator { + fun validateConstraints(obj: Any?) { + obj?.let { + Validation.byDefaultProvider() + .configure() + .messageInterpolator(ParameterMessageInterpolator()) + .buildValidatorFactory().use { factory -> + val violations = factory.validator.validate(obj) + if (violations.isNotEmpty()) { + throw PropertyConstraintViolationException( + "Some field constraints have been violated", + violations.stream().map { toConstraintViolation(it) }.collect(Collectors.toList()) + ) + } + } + } + } + + private fun toConstraintViolation(violation: ConstraintViolation<*>): PropertyConstraintViolation { + return PropertyConstraintViolation( + violation.propertyPath.iterator().next().name, + violation.propertyPath.toString(), + violation.message + ) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt new file mode 100644 index 00000000..06a72589 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql + +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Query +import com.apollographql.java.client.ApolloClient +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.v2.core.configuration.DefaultClientBuilder +import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.v2.core.client.ApiClientApolloHttpEngine +import com.expediagroup.sdk.v2.core.client.util.createApiClient +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration +import java.util.concurrent.CompletableFuture + +class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecutor { + private val engine: ApiClientApolloHttpEngine = ApiClientApolloHttpEngine( + createApiClient( + configuration = configuration + ) + ) + + companion object { + @JvmStatic + fun builder() = ClientConfiguration.builder() + } + + private val apolloClient: ApolloClient = ApolloClient.Builder() + .serverUrl(configuration.getEndpoint()) + .httpEngine(engine) + .build() + + class Builder(private val namespace: String) : DefaultClientBuilder() { + override fun build(): BaseGraphQLClient { + return BaseGraphQLClient(this.buildConfiguration()) + } + } + + /** + * Executes a GraphQL query and returns the result. + * + * @param query The GraphQL query to execute. + * @return The result of the query execution, with errors handled. + * @throws ExpediaGroupServiceException If the query execution returns errors. + */ + override fun execute(query: Query): CompletableFuture { + val promise: CompletableFuture = CompletableFuture() + + apolloClient.query(query).enqueue { response -> + try { + if (response.hasErrors()) { + // Complete exceptionally if there are GraphQL errors + promise.completeExceptionally(ExpediaGroupServiceException(message = response.errors.toString())) + } else if (response.exception != null) { + // Complete exceptionally if there is a network or other exception + promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) + } else { + // Complete normally with the response data if no errors or exceptions + promise.complete(response.dataAssertNoErrors) + } + } catch (e: Exception) { + // Handle unexpected exceptions during callback execution + promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) + } + } + + return promise + } + + + /** + * Executes a GraphQL mutation and returns the result. + * + * @param mutation The GraphQL mutation to execute. + * @return The result of the mutation execution, with errors handled. + * @throws ExpediaGroupServiceException If the mutation execution returns errors. + */ + override fun execute(mutation: Mutation): CompletableFuture { + val promise: CompletableFuture = CompletableFuture() + + apolloClient.mutation(mutation).enqueue { response -> + try { + if (response.hasErrors()) { + // Complete exceptionally if there are GraphQL errors + promise.completeExceptionally(ExpediaGroupServiceException(response.errors.toString())) + } else if (response.exception != null) { + // Complete exceptionally if there is a network or other exception + promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) + } else { + // Complete normally with the response data if no errors or exceptions + promise.complete(response.dataAssertNoErrors) + } + } catch (e: Exception) { + // Handle unexpected exceptions during callback execution + promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) + } + } + + return promise + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt new file mode 100644 index 00000000..3c1f1884 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql + +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Query +import java.util.concurrent.CompletableFuture + +interface GraphQLExecutor { + fun execute(query: Query): CompletableFuture + fun execute(mutation: Mutation): CompletableFuture +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt new file mode 100644 index 00000000..4a7cc1aa --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter + +import com.apollographql.apollo.api.Adapter +import com.apollographql.apollo.api.CustomScalarAdapters +import com.apollographql.apollo.api.json.JsonReader +import com.apollographql.apollo.api.json.JsonWriter +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + +/** + * Converts the custom scalar `DateTime` to and from `java.time.OffsetDateTime`. + */ +object DateTimeAdapter : Adapter { + override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): OffsetDateTime? { + val dateString = reader.nextString() ?: return null + return OffsetDateTime.parse(dateString, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + } + + override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: OffsetDateTime?) { + if (value != null) { + writer.value(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + } else { + writer.nullValue() + } + } +} + + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt new file mode 100644 index 00000000..54a4756e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter + +import com.apollographql.apollo.api.Adapter +import com.apollographql.apollo.api.CustomScalarAdapters +import com.apollographql.apollo.api.json.JsonReader +import com.apollographql.apollo.api.json.JsonWriter +import java.net.MalformedURLException +import java.net.URI +import java.net.URISyntaxException +import java.net.URL + +/** + * Converts the custom scalar `Url` to and from `java.net.URL`. + */ +object URLAdapter : Adapter { + + override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): URL? { + val urlString = reader.nextString() ?: return null + return try { + URI(urlString).toURL() + } catch (e: URISyntaxException) { + throw IllegalStateException("Invalid URI format: $urlString", e) + } catch (e: MalformedURLException) { + throw IllegalStateException("Invalid URL format: $urlString", e) + } + } + + override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: URL?) { + if (value != null) { + writer.value(value.toString()) + } else { + writer.nullValue() + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt new file mode 100644 index 00000000..3fde3ce3 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter + +import com.apollographql.apollo.api.Adapter +import com.apollographql.apollo.api.CustomScalarAdapters +import com.apollographql.apollo.api.json.JsonReader +import com.apollographql.apollo.api.json.JsonWriter +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +/** + * Converts ZoneDateTime custom scalar `Url` to and from `java.time.format.DateTimeFormatter`. + */ +object ZoneDateTimeAdapter : Adapter { + override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): ZonedDateTime? { + val dateString = reader.nextString() ?: return null + return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_ZONED_DATE_TIME) + } + + override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: ZonedDateTime?) { + if (value != null) { + writer.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) + } else { + writer.nullValue() + } + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt new file mode 100644 index 00000000..51ec7797 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.payment + +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor + +/** + * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. + * + * This client is configured with a `ClientConfiguration` that includes authentication details, + * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). + * + * @constructor Creates a new instance of `PaymentClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, + * timeouts, and logging masking options. + * + * Example usage: + * ``` + * PaymentClient( + * ClientConfiguration + * .builder() + * .key("API_KEY") + * .secret("API_SECRET") + * .build() + * ) + * ``` + */ +class PaymentClient(config: ClientConfiguration): + GraphQLExecutor by BaseGraphQLClient( + config.toFullClientConfiguration( + endpointProvider = EndpointProvider::getPaymentClientEndpoint, + authEndpointProvider = EndpointProvider::getAuthEndpoint + ), + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt new file mode 100644 index 00000000..49451a62 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.sandbox + +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor + +/** + * A client for interacting with EG Lodging Connectivity Sandbox GraphQL API. + * + * This client is configured with a `ClientConfiguration` that includes authentication details, + * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). + * + * @constructor Creates a new instance of `SandboxDataManagementClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, + * timeouts, and logging masking options. + * + * Example usage: + * ``` + * SandboxDataManagementClient( + * ClientConfiguration + * .builder() + * .key("API_KEY") + * .secret("API_SECRET") + * .build() + * ) + * ``` + */ +class SandboxDataManagementClient(config: ClientConfiguration) : + GraphQLExecutor by BaseGraphQLClient( + config.toFullClientConfiguration( + endpointProvider = EndpointProvider::getSandboxDataManagementClientEndpoint, + authEndpointProvider = EndpointProvider::getAuthEndpoint, + defaultEnvironment = ClientEnvironment.SANDBOX_PROD + ), + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt new file mode 100644 index 00000000..9ab8487c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.supply + +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor + +/** + * A client for interacting with EG Lodging Connectivity Supply GraphQL API that exposes various lodging capabilities + * such as reservations, promotions, reviews, notifications, messaging, etc... + * + * This client is configured with a `ClientConfiguration` that includes authentication details, + * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). + * + * In addition, this client can be configured to target the sandbox environment by passing `ClientEnvironment.SANDBOX_PROD` or + * `ClientEnvironment.SANDBOX_TEST` to the `environment` configuration option. + * + * @constructor Creates a new instance of `SupplyClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, + * timeouts, and logging masking options. + * + * Example usage: + * ``` + * SupplyClient( + * ClientConfiguration + * .builder() + * .key("API_KEY") + * .secret("API_SECRET") + * .build() + * ) + * ``` + */ +class SupplyClient(config: ClientConfiguration) : + GraphQLExecutor by BaseGraphQLClient( + config.toFullClientConfiguration( + endpointProvider = EndpointProvider::getSupplyClientEndpoint, + authEndpointProvider = EndpointProvider::getAuthEndpoint + ), + ) diff --git a/code/src/main/resources/test.txt b/code/src/main/resources/test.txt new file mode 100644 index 00000000..bdf08de0 --- /dev/null +++ b/code/src/main/resources/test.txt @@ -0,0 +1 @@ +test file \ No newline at end of file diff --git a/code/tasks-gradle/generateProperties.gradle b/code/tasks-gradle/generateProperties.gradle new file mode 100644 index 00000000..0f9d5263 --- /dev/null +++ b/code/tasks-gradle/generateProperties.gradle @@ -0,0 +1,24 @@ +// Task to generate sdk.properties file in resources directory +tasks.register('generateSdkProperties') { + def resourcesDir = "$projectDir/src/main/resources" + def propertiesFile = file("$resourcesDir/sdk.properties") + + doLast { + if (!file(resourcesDir).exists()) { + file(resourcesDir).mkdirs() + } + + propertiesFile.withWriter('UTF-8') { writer -> + writer.writeLine("version=${version}") + writer.writeLine("artifactId=${rootProject.name}") + writer.writeLine("userAgentPrefix=expediagroup-sdk-java" + + "-${rootProject.name.replaceFirst("-sdk", "")}") + } + + println "Generated sdk.properties in $resourcesDir" + } +} + +// Ensure generateSdkProperties runs before compilation +compileKotlin.dependsOn generateSdkProperties +compileJava.dependsOn generateSdkProperties diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java new file mode 100644 index 00000000..a90cff2f --- /dev/null +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java @@ -0,0 +1,26 @@ +package com.expediagroup.sdk.lodgingconnectivity; + +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client.FileManagementClient; + +import java.io.File; +import java.io.IOException; + +public class FileManagementExample { + static FileManagementClient client = new FileManagementClient( + ClientConfiguration.builder() + .key("KEY") + .secret("SECRET") + .environment(ClientEnvironment.TEST) + .build() + ); + + public static void main(String[] args) throws IOException { + client.upload( + new File("code/src/main/resources/test.txt"), + "messageThreadId", + "6ddb1317-bf3d-4759-bcdf-d52642cfac88" + ); + } +} diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java new file mode 100644 index 00000000..ce093df5 --- /dev/null +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java @@ -0,0 +1,26 @@ +package com.expediagroup.sdk.lodgingconnectivity.v2; + +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; +import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client.FileManagementClient; + +import java.io.File; +import java.io.IOException; + +public class FileManagementExample { + static FileManagementClient client = new FileManagementClient( + ClientConfiguration.builder() + .key("KEY") + .secret("SECRET") + .environment(ClientEnvironment.TEST) + .build() + ); + + public static void main(String[] args) throws IOException { + client.upload( + new File("code/src/main/resources/test.txt"), + "messageThreadId", + "6ddb1317-bf3d-4759-bcdf-d52642cfac88" + ); + } +} diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java new file mode 100644 index 00000000..84190e4a --- /dev/null +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.v2; + +import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.*; +import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.*; +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; +import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; +import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +/** + * Example class to demonstrate different operations supported by the LodgingSupplySandboxDataManagementClient + * Run the main method to see these operations in action: + * 1. Create a Property + * 2. Update Property Name + * 3. Create a Reservation + * 4. Update the Reservation + * 5. Update the Reservation's Stay Dates + * 6. Cancel the Reservation + * 7. Delete the Reservation + * 8. Delete the Property + **/ +public class LodgingSupplySandboxDataManagementClientUsageExample { + + private static final SandboxDataManagementClient client = new SandboxDataManagementClient( + ClientConfiguration + .builder() + .key("KEY") + .secret("SECRET") + .environment(ClientEnvironment.TEST) + .build() + ); + + private static final String PROPERTY_NAME = "Lodging SDK Test Property"; + private static final String UPDATED_PROPERTY_NAME = "New Lodging SDK Test Property"; + + public static void main(String[] args) throws ExecutionException, InterruptedException { + // Delete any old property if it has the same name used in the test run + deletePropertyIfExists(); + + // ******* Create Property ******* + CreatePropertyInput createPropertyInput = CreatePropertyInput.builder().name(Optional.of(PROPERTY_NAME)).build(); + var createPropertyResponse = client.execute(new SandboxCreatePropertyMutation(createPropertyInput)); + + + var propertyId = createPropertyResponse.get().createProperty.property.id; + + // ******* Update Property Name ******* + var updatePropertyInput = UpdatePropertyInput.builder().id(propertyId).name(Optional.of(UPDATED_PROPERTY_NAME)).build(); + var updatePropertyResponse = client.execute(new SandboxUpdatePropertyMutation(updatePropertyInput)); + + // ******* Create Reservation ******* + var createReservationInput = CreateReservationInput.builder().propertyId(propertyId).childCount(Optional.of(4)).adultCount(Optional.of(2)).build(); + var createReservationResponse = client.execute(new SandboxCreateReservationMutation(createReservationInput)); + + + var reservationId = createReservationResponse.get().createReservation.reservation.sandboxReservationFragment.id; + + // ******* Update Reservation ******* + var updateReservationInput = UpdateReservationInput.builder().id(reservationId).childAges(Optional.of(List.of(3, 5, 7))).build(); + var updateReservationResponse = client.execute(new SandboxUpdateReservationMutation(updateReservationInput)); + + // ******* Update Reservation Stay Dates ******* + var changeStayDatesInput = ChangeReservationStayDatesInput + .builder() + .id(reservationId) + .checkInDate(LocalDate.of(2024, 6, 5)) + .checkOutDate(LocalDate.of(2024, 6, 10)) + .build(); + + var changeStayDatesResponse = client.execute(new SandboxChangeReservationStayDatesMutation(changeStayDatesInput)); + + // ******* Cancel Reservation ******* + var cancelReservationInput = CancelReservationInput.builder().id(reservationId).sendNotification(Optional.of(false)).build(); + var cancelReservationResponse = client.execute(new SandboxCancelReservationMutation(cancelReservationInput)); + + // ******* Delete Reservation ******* + var deleteReservationInput = DeleteReservationInput.builder().id(reservationId).build(); + var deleteReservationResponse = client.execute(new SandboxDeleteReservationMutation(deleteReservationInput)); + + // ******* Delete Property ******* + var deletePropertyInput = DeletePropertyInput.builder().id(propertyId).build(); + var deletePropertyResponse = client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); + +// System.exit(0); + } + + private static void deletePropertyIfExists() throws ExecutionException, InterruptedException { + var propertiesQuery = SandboxPropertiesQuery.builder().skipReservations(true).build(); + var propertiesResponse = client.execute(propertiesQuery); + + propertiesResponse.get().properties.elements.forEach(property -> { + if (property.name.equals(PROPERTY_NAME) || property.name.equals(UPDATED_PROPERTY_NAME)) { + var deletePropertyInput = DeletePropertyInput.builder().id(property.id).build(); + client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); + } + }); + } +} diff --git a/examples/src/main/resources/simplelogger.properties b/examples/src/main/resources/simplelogger.properties new file mode 100644 index 00000000..eafa2b0f --- /dev/null +++ b/examples/src/main/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel=debug \ No newline at end of file From 7956316766795311fde161b1d6b307f408500fd5 Mon Sep 17 00:00:00 2001 From: Omar Aljarrah <50204418+OmarAlJarrah@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:58:32 +0300 Subject: [PATCH 02/15] refactor: use manifest for user agent (#82) PR: https://github.com/ExpediaGroup/lodging-connectivity-java-sdk/pull/82 --- code/build.gradle | 13 +++++++--- .../sdk/v2/core/configuration/SdkMetadata.kt | 16 +++++-------- .../sdk/v2/core/model/UserAgent.kt | 3 ++- code/tasks-gradle/generateProperties.gradle | 24 ------------------- 4 files changed, 18 insertions(+), 38 deletions(-) delete mode 100644 code/tasks-gradle/generateProperties.gradle diff --git a/code/build.gradle b/code/build.gradle index 264fc20f..a0ef0ab3 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -21,6 +21,16 @@ sourceSets { } } +jar { + manifest { + attributes( + "version": version, + "artifactId": rootProject.name, + "userAgentPrefix": "expediagroup-sdk-java-${rootProject.name.replaceFirst("-sdk", "")}" + ) + } +} + dependencies { /* Kotlin */ implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.20' @@ -57,9 +67,6 @@ dependencies { implementation 'jakarta.validation:jakarta.validation-api:2.0.2' } -apply from: "tasks-gradle/generateProperties.gradle" apply from: "tasks-gradle/apollo.gradle" apply from: "tasks-gradle/publishing.gradle" apply from: "tasks-gradle/dokka.gradle" - - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt index 02708729..b1cd4b13 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt @@ -1,8 +1,6 @@ package com.expediagroup.sdk.v2.core.configuration -import com.google.common.io.Resources -import java.io.FileInputStream -import java.util.Properties +import java.util.jar.Manifest internal object SdkMetadata { private lateinit var artifactId: String @@ -14,13 +12,11 @@ internal object SdkMetadata { fun getUserAgentPrefix(): String = userAgentPrefix init { - Resources.getResource("sdk.properties").file?.let { path -> - Properties().apply { - load(FileInputStream(path)) - }.also { properties -> - artifactId = properties.getProperty("artifactId") - version = properties.getProperty("version") - userAgentPrefix = properties.getProperty("userAgentPrefix") + this::class.java.classLoader.getResourceAsStream("META-INF/MANIFEST.MF").use { + Manifest(it).apply { + artifactId = mainAttributes.getValue("artifactId") + version = mainAttributes.getValue("version") + userAgentPrefix = mainAttributes.getValue("userAgentPrefix") } } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt index 8d21bb5a..d63c902f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt @@ -14,7 +14,8 @@ data class UserAgent( val operatingSystem: String, ) { private val userAgentPrefix: String = SdkMetadata.getUserAgentPrefix() + private val version: String = SdkMetadata.getVersion() override fun toString(): String = - "$userAgentPrefix ($jdkVersion; $operatingSystem)" + "$userAgentPrefix/$version ($jdkVersion; $operatingSystem)" } diff --git a/code/tasks-gradle/generateProperties.gradle b/code/tasks-gradle/generateProperties.gradle deleted file mode 100644 index 0f9d5263..00000000 --- a/code/tasks-gradle/generateProperties.gradle +++ /dev/null @@ -1,24 +0,0 @@ -// Task to generate sdk.properties file in resources directory -tasks.register('generateSdkProperties') { - def resourcesDir = "$projectDir/src/main/resources" - def propertiesFile = file("$resourcesDir/sdk.properties") - - doLast { - if (!file(resourcesDir).exists()) { - file(resourcesDir).mkdirs() - } - - propertiesFile.withWriter('UTF-8') { writer -> - writer.writeLine("version=${version}") - writer.writeLine("artifactId=${rootProject.name}") - writer.writeLine("userAgentPrefix=expediagroup-sdk-java" + - "-${rootProject.name.replaceFirst("-sdk", "")}") - } - - println "Generated sdk.properties in $resourcesDir" - } -} - -// Ensure generateSdkProperties runs before compilation -compileKotlin.dependsOn generateSdkProperties -compileJava.dependsOn generateSdkProperties From 06c00cea41c11ece811b53d3ba83884cd4fd2b43 Mon Sep 17 00:00:00 2001 From: Omar Aljarrah <50204418+OmarAlJarrah@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:14:44 +0300 Subject: [PATCH 03/15] feat: graphql executor sync and async execute method (#83) PR: https://github.com/ExpediaGroup/lodging-connectivity-java-sdk/pull/83 --- .../sdk/v2/core/client/util/ApiClientUtil.kt | 3 +- .../graphql/BaseGraphQLClient.kt | 12 +++-- .../graphql/GraphQLExecutor.kt | 6 ++- ...ndboxDataManagementClientUsageExample.java | 49 +++++++++---------- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt index b7d07515..9f3a1c32 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt @@ -11,9 +11,8 @@ import com.google.api.client.http.GenericUrl import com.google.api.client.http.HttpTransport /** - * Creates an instance of `ApiClient` using the provided namespace, client configuration, and optional HTTP transport. + * Creates an instance of `ApiClient` using the provided client configuration, and optional HTTP transport. * - * @param namespace The namespace to be used for creating the API client. * @param configuration The client configuration implementing `ClientConfiguration` interface. Must also implement `EndpointTrait`. * @param transport An optional `HttpTransport` object. If not provided, a default transport will be used. * @return An instance of `ApiClient`. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt index 06a72589..aa9ee0fb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt @@ -44,7 +44,7 @@ class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecuto .httpEngine(engine) .build() - class Builder(private val namespace: String) : DefaultClientBuilder() { + class Builder() : DefaultClientBuilder() { override fun build(): BaseGraphQLClient { return BaseGraphQLClient(this.buildConfiguration()) } @@ -57,7 +57,7 @@ class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecuto * @return The result of the query execution, with errors handled. * @throws ExpediaGroupServiceException If the query execution returns errors. */ - override fun execute(query: Query): CompletableFuture { + override fun executeAsync(query: Query): CompletableFuture { val promise: CompletableFuture = CompletableFuture() apolloClient.query(query).enqueue { response -> @@ -81,6 +81,9 @@ class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecuto return promise } + override fun execute(query: Query): T = + executeAsync(query).get() + /** * Executes a GraphQL mutation and returns the result. @@ -89,7 +92,7 @@ class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecuto * @return The result of the mutation execution, with errors handled. * @throws ExpediaGroupServiceException If the mutation execution returns errors. */ - override fun execute(mutation: Mutation): CompletableFuture { + override fun executeAsync(mutation: Mutation): CompletableFuture { val promise: CompletableFuture = CompletableFuture() apolloClient.mutation(mutation).enqueue { response -> @@ -112,4 +115,7 @@ class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecuto return promise } + + override fun execute(mutation: Mutation): T = + executeAsync(mutation).get() } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt index 3c1f1884..06873592 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt @@ -21,6 +21,8 @@ import com.apollographql.apollo.api.Query import java.util.concurrent.CompletableFuture interface GraphQLExecutor { - fun execute(query: Query): CompletableFuture - fun execute(mutation: Mutation): CompletableFuture + fun executeAsync(query: Query): CompletableFuture + fun executeAsync(mutation: Mutation): CompletableFuture + fun execute(query: Query): T + fun execute(mutation: Mutation): T } diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java index 84190e4a..1ec63b62 100644 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java @@ -23,7 +23,8 @@ import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; import java.time.LocalDate; -import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -59,59 +60,57 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc // ******* Create Property ******* CreatePropertyInput createPropertyInput = CreatePropertyInput.builder().name(Optional.of(PROPERTY_NAME)).build(); - var createPropertyResponse = client.execute(new SandboxCreatePropertyMutation(createPropertyInput)); + SandboxCreatePropertyMutation.Data createPropertyResponse = client.executeAsync(new SandboxCreatePropertyMutation(createPropertyInput)).get(); - var propertyId = createPropertyResponse.get().createProperty.property.id; + String propertyId = createPropertyResponse.createProperty.property.id; // ******* Update Property Name ******* - var updatePropertyInput = UpdatePropertyInput.builder().id(propertyId).name(Optional.of(UPDATED_PROPERTY_NAME)).build(); - var updatePropertyResponse = client.execute(new SandboxUpdatePropertyMutation(updatePropertyInput)); + UpdatePropertyInput updatePropertyInput = UpdatePropertyInput.builder().id(propertyId).name(Optional.of(UPDATED_PROPERTY_NAME)).build(); + SandboxUpdatePropertyMutation.Data updatePropertyResponse = client.executeAsync(new SandboxUpdatePropertyMutation(updatePropertyInput)).get(); // ******* Create Reservation ******* - var createReservationInput = CreateReservationInput.builder().propertyId(propertyId).childCount(Optional.of(4)).adultCount(Optional.of(2)).build(); - var createReservationResponse = client.execute(new SandboxCreateReservationMutation(createReservationInput)); + CreateReservationInput createReservationInput = CreateReservationInput.builder().propertyId(propertyId).childCount(Optional.of(4)).adultCount(Optional.of(2)).build(); + SandboxCreateReservationMutation.Data createReservationResponse = client.executeAsync(new SandboxCreateReservationMutation(createReservationInput)).get(); - var reservationId = createReservationResponse.get().createReservation.reservation.sandboxReservationFragment.id; + String reservationId = createReservationResponse.createReservation.reservation.sandboxReservationFragment.id; // ******* Update Reservation ******* - var updateReservationInput = UpdateReservationInput.builder().id(reservationId).childAges(Optional.of(List.of(3, 5, 7))).build(); - var updateReservationResponse = client.execute(new SandboxUpdateReservationMutation(updateReservationInput)); + UpdateReservationInput updateReservationInput = UpdateReservationInput.builder().id(reservationId).childAges(Optional.of(new ArrayList<>(Arrays.asList(3, 5, 7)))).build(); + SandboxUpdateReservationMutation.Data updateReservationResponse = client.executeAsync(new SandboxUpdateReservationMutation(updateReservationInput)).get(); // ******* Update Reservation Stay Dates ******* - var changeStayDatesInput = ChangeReservationStayDatesInput + ChangeReservationStayDatesInput changeStayDatesInput = ChangeReservationStayDatesInput .builder() .id(reservationId) .checkInDate(LocalDate.of(2024, 6, 5)) .checkOutDate(LocalDate.of(2024, 6, 10)) .build(); - var changeStayDatesResponse = client.execute(new SandboxChangeReservationStayDatesMutation(changeStayDatesInput)); + SandboxChangeReservationStayDatesMutation.Data changeStayDatesResponse = client.executeAsync(new SandboxChangeReservationStayDatesMutation(changeStayDatesInput)).get(); // ******* Cancel Reservation ******* - var cancelReservationInput = CancelReservationInput.builder().id(reservationId).sendNotification(Optional.of(false)).build(); - var cancelReservationResponse = client.execute(new SandboxCancelReservationMutation(cancelReservationInput)); + CancelReservationInput cancelReservationInput = CancelReservationInput.builder().id(reservationId).sendNotification(Optional.of(false)).build(); + SandboxCancelReservationMutation.Data cancelReservationResponse = client.executeAsync(new SandboxCancelReservationMutation(cancelReservationInput)).get(); // ******* Delete Reservation ******* - var deleteReservationInput = DeleteReservationInput.builder().id(reservationId).build(); - var deleteReservationResponse = client.execute(new SandboxDeleteReservationMutation(deleteReservationInput)); + DeleteReservationInput deleteReservationInput = DeleteReservationInput.builder().id(reservationId).build(); + SandboxDeleteReservationMutation.Data deleteReservationResponse = client.executeAsync(new SandboxDeleteReservationMutation(deleteReservationInput)).get(); // ******* Delete Property ******* - var deletePropertyInput = DeletePropertyInput.builder().id(propertyId).build(); - var deletePropertyResponse = client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); - -// System.exit(0); + DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(propertyId).build(); + SandboxDeletePropertyMutation.Data deletePropertyResponse = client.executeAsync(new SandboxDeletePropertyMutation(deletePropertyInput)).get(); } private static void deletePropertyIfExists() throws ExecutionException, InterruptedException { - var propertiesQuery = SandboxPropertiesQuery.builder().skipReservations(true).build(); - var propertiesResponse = client.execute(propertiesQuery); + SandboxPropertiesQuery propertiesQuery = SandboxPropertiesQuery.builder().skipReservations(true).build(); + SandboxPropertiesQuery.Data propertiesResponse = client.executeAsync(propertiesQuery).get(); - propertiesResponse.get().properties.elements.forEach(property -> { + propertiesResponse.properties.elements.forEach(property -> { if (property.name.equals(PROPERTY_NAME) || property.name.equals(UPDATED_PROPERTY_NAME)) { - var deletePropertyInput = DeletePropertyInput.builder().id(property.id).build(); - client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); + DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(property.id).build(); + client.executeAsync(new SandboxDeletePropertyMutation(deletePropertyInput)); } }); } From 2c9fc51fca795fa5cfc92705f6c9b2e159baddb5 Mon Sep 17 00:00:00 2001 From: Omar Aljarrah <50204418+OmarAlJarrah@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:00:32 +0300 Subject: [PATCH 04/15] refactor: cleaup package versioning and set v2 as default (#84) Co-authored-by: mdwairi --- code/build.gradle | 18 +- .../apache/util/ApacheHttpTransportUtil.kt | 10 +- ...2CredentialsWithRefreshBuilderExtension.kt | 4 +- .../strategy/AuthenticationStrategy.kt | 4 +- .../BearerAuthenticationHandlerFactory.kt | 24 +- .../util/HttpCredentialsAdapterUtil.kt | 16 +- .../sdk/{v2 => }/core/client/ApiClient.kt | 12 +- .../core/client/ApiClientApolloHttpEngine.kt | 4 +- .../{v2 => }/core/client/ApiClientBuilder.kt | 6 +- .../expediagroup/sdk/core/client/Client.kt | 328 ------------------ .../sdk/core/client/ClientHelpers.kt | 21 -- .../sdk/core/client/Environment.kt | 35 -- .../sdk/core/client/ExpediaGroupClient.kt | 70 ---- .../sdk/core/client/HttpHandler.kt | 44 --- .../sdk/core/client/OkHttpEventListener.kt | 185 ---------- .../sdk/{v2 => }/core/client/SdkClient.kt | 12 +- .../core/client/util/ApiClientUtil.kt | 16 +- .../core/client/util/GsonFactoryUtil.kt | 2 +- .../core/configuration/ClientConfiguration.kt | 42 --- .../sdk/core/configuration/Credentials.kt | 45 --- .../configuration/DefaultClientBuilder.kt | 2 +- .../ExpediaGroupClientConfiguration.kt | 47 --- .../ExpediaGroupDefaultClientConfiguration.kt | 8 +- .../configuration/FullClientConfiguration.kt | 21 +- .../core/configuration/SdkMetadata.kt | 2 +- .../collector/ConfigurationCollector.kt | 73 ---- .../collector/ConfigurationProviderQueue.kt | 47 --- .../provider/ConfigurationProvider.kt | 58 ---- .../ExpediaGroupConfigurationProvider.kt | 43 --- .../provider/RapidConfigurationProvider.kt | 40 --- .../provider/RuntimeConfigurationProvider.kt | 45 --- .../sdk/core/constant/Authentication.kt | 28 +- .../sdk/core/constant/ConfigurationName.kt | 40 --- .../sdk/core/constant/Constant.kt | 15 +- .../sdk/core/constant/LogMaskingFields.kt | 4 +- .../sdk/core/constant/LogMaskingRegex.kt | 22 -- .../sdk/core/constant/LoggingMessage.kt | 6 +- .../sdk/core/constant/SignatureValues.kt | 30 -- .../provider/LogMaskingRegexProvider.kt | 20 -- .../provider/LoggingMessageProvider.kt | 5 +- .../sdk/core/contract/Contract.kt | 35 -- .../{v2 => }/core/http/BlobTypeDetector.kt | 2 +- .../expediagroup/sdk/core/http/HttpStatus.kt | 93 +++++ .../sdk/{v2 => }/core/jackson/Config.kt | 2 +- .../sdk/{v2 => }/core/jackson/Util.kt | 2 +- .../core/logging/ExpediaGroupLogger.kt | 8 +- .../core/logging/ExpediaGroupLoggerFactory.kt | 2 +- .../core/logging/LogMessageConstants.kt | 2 +- .../{v2 => }/core/logging/LogMessageTag.kt | 2 +- .../core/logging/LoggableContentTypes.kt | 2 +- .../mask/ExpediaGroupJsonFieldFilter.kt | 2 +- .../ExpediaGroupJsonFieldPatternBuilder.kt | 2 +- .../{v2 => }/core/logging/mask/MaskLogs.kt | 2 +- .../expediagroup/sdk/core/model/Headers.kt | 24 -- .../sdk/{v2 => }/core/model/Operation.kt | 2 +- .../{v2 => }/core/model/OperationParams.kt | 2 +- .../expediagroup/sdk/core/model/Response.kt | 10 +- .../sdk/{v2 => }/core/model/UserAgent.kt | 4 +- .../core/model/exception/ExceptionUtils.kt | 40 --- .../ExpediaGroupInvalidFieldNameException.kt | 8 +- .../service/ExpediaGroupApiException.kt | 21 +- .../service/ExpediaGroupAuthException.kt | 8 +- ...xpediaGroupServiceDefaultErrorException.kt | 18 - .../sdk/core/model/paging/Paginator.kt | 90 ----- .../sdk/core/model/paging/ResponseState.kt | 109 ------ .../com/expediagroup/sdk/core/plugin/Hook.kt | 75 ---- .../expediagroup/sdk/core/plugin/Plugin.kt | 43 --- .../sdk/core/plugin/PluginConfiguration.kt | 23 -- .../AuthenticationConfiguration.kt | 38 -- .../AuthenticationHookFactory.kt | 67 ---- .../authentication/AuthenticationPlugin.kt | 42 --- .../strategy/AuthenticationStrategy.kt | 51 --- .../ExpediaGroupAuthenticationStrategy.kt | 157 --------- .../strategy/RapidAuthenticationStrategy.kt | 61 ---- .../plugin/encoding/EncodingConfiguration.kt | 28 -- .../core/plugin/encoding/EncodingPlugin.kt | 31 -- .../ExceptionHandlingConfiguration.kt | 28 -- .../exception/ExceptionHandlingPlugin.kt | 37 -- .../httptimeout/HttpTimeoutConfiguration.kt | 36 -- .../plugin/httptimeout/HttpTimeoutPlugin.kt | 33 -- .../core/plugin/logging/ExpediaGroupLogger.kt | 33 -- .../logging/ExpediaGroupLoggerFactory.kt | 28 -- .../sdk/core/plugin/logging/LogMasker.kt | 42 --- .../plugin/logging/LoggingConfiguration.kt | 48 --- .../logging/LoggingMaskedFieldsProvider.kt | 45 --- .../sdk/core/plugin/logging/LoggingPlugin.kt | 50 --- .../core/plugin/logging/RequestBodyLogger.kt | 68 ---- .../core/plugin/logging/ResponseBodyLogger.kt | 75 ---- .../request/DefaultRequestConfiguration.kt | 32 -- .../plugin/request/DefaultRequestPlugin.kt | 31 -- .../SerializationConfiguration.kt | 30 -- .../serialization/SerializationPlugin.kt | 41 --- .../sdk/{v2 => }/core/request/Request.kt | 6 +- .../ApiClientRequestInitializer.kt | 4 +- .../initializer/InitializerFunctions.kt | 6 +- .../initializer/SdkRequestInitializer.kt | 2 +- .../HttpRequestLoggingInterceptor.kt | 18 +- .../HttpResponseLoggingInterceptor.kt | 18 +- .../sdk/{v2 => }/core/trait/Trait.kt | 2 +- .../AuthenticationHandlerTrait.kt | 6 +- .../authentication/RefreshAccessTokenTrait.kt | 4 +- .../core/trait/common/BuilderTrait.kt | 4 +- .../core/trait/common/ConfigurationTrait.kt | 4 +- .../sdk/{v2 => }/core/trait/common/IdTrait.kt | 4 +- .../trait/configuration/AuthEndpointTrait.kt | 2 +- .../AuthenticationStrategyTrait.kt | 4 +- .../configuration/ClientConfiguration.kt | 6 +- .../configuration/ClientConfigurationTrait.kt | 4 +- .../configuration/ConnectionTimeoutTrait.kt | 2 +- .../core/trait/configuration/EndpointTrait.kt | 2 +- .../configuration/FullConfigurationTrait.kt | 2 +- .../core/trait/configuration/KeyTrait.kt | 2 +- .../MaskedLoggingBodyFieldsTrait.kt | 2 +- .../MaskedLoggingHeadersTrait.kt | 2 +- .../MaxConnectionsPerRouteTrait.kt | 2 +- .../configuration/MaxConnectionsTotalTrait.kt | 2 +- .../configuration/RequestTimeoutTrait.kt | 2 +- .../core/trait/configuration/SecretTrait.kt | 2 +- .../trait/configuration/SocketTimeoutTrait.kt | 2 +- .../ToClientConfigurationTrait.kt | 4 +- .../configuration/ClientConfiguration.kt | 75 +++- .../configuration/ClientEnvironment.kt | 1 - .../configuration/EndpointProvider.kt | 15 +- .../client/FileManagementClient.kt | 31 +- .../models/FileUploadRequest.kt | 2 +- .../filemanagement/models/GenericError.kt | 3 +- .../models/Upload201Response.kt | 3 +- .../exception/PropertyConstraintViolation.kt | 2 +- .../PropertyConstraintViolationException.kt | 2 +- .../operations/FileDownloadOperation.kt | 8 +- .../operations/FileUploadOperation.kt | 21 +- .../PropertyConstraintsValidator.kt | 6 +- .../graphql/BaseGraphQLClient.kt | 116 ++++--- .../graphql/GraphQLExecutor.kt | 29 +- .../graphql/adapter/DateTimeAdapter.kt | 2 - .../graphql/adapter/ZoneDateTimeAdapter.kt | 1 - .../graphql/payment/PaymentClient.kt | 8 +- .../sandbox/SandboxDataManagementClient.kt | 8 +- .../graphql/supply/SupplyClient.kt | 14 +- .../sdk/v2/core/constant/Authentication.kt | 5 - .../sdk/v2/core/constant/Constant.kt | 35 -- .../sdk/v2/core/constant/ExceptionMessage.kt | 24 -- .../sdk/v2/core/constant/HeaderKey.kt | 26 -- .../sdk/v2/core/constant/HeaderValue.kt | 20 -- .../expediagroup/sdk/v2/core/constant/Key.kt | 26 -- .../sdk/v2/core/constant/LogMaskingFields.kt | 36 -- .../sdk/v2/core/constant/LoggerName.kt | 21 -- .../sdk/v2/core/constant/LoggingMessage.kt | 28 -- .../expediagroup/sdk/v2/core/model/Headers.kt | 25 -- .../expediagroup/sdk/v2/core/model/Nothing.kt | 21 -- .../sdk/v2/core/model/Properties.kt | 36 -- .../sdk/v2/core/model/Response.kt | 66 ---- .../sdk/v2/core/model/TransactionId.kt | 30 -- .../model/exception/ExpediaGroupException.kt | 27 -- .../client/ExpediaGroupClientException.kt | 29 -- .../ExpediaGroupConfigurationException.kt | 27 -- .../ExpediaGroupInvalidFieldNameException.kt | 30 -- .../service/ExpediaGroupAuthException.kt | 43 --- ...xpediaGroupServiceDefaultErrorException.kt | 21 -- .../service/ExpediaGroupServiceException.kt | 30 -- .../configuration/ClientConfiguration.kt | 195 ----------- .../configuration/ClientEnvironment.kt | 38 -- .../configuration/EndpointProvider.kt | 103 ------ .../models/exception/ApiException.kt | 80 ----- .../graphql/BaseGraphQLClient.kt | 121 ------- .../graphql/GraphQLExecutor.kt | 28 -- .../graphql/adapter/DateTimeAdapter.kt | 44 --- .../graphql/adapter/URLAdapter.kt | 51 --- .../graphql/adapter/ZoneDateTimeAdapter.kt | 43 --- .../graphql/payment/PaymentClient.kt | 51 --- .../sandbox/SandboxDataManagementClient.kt | 53 --- .../graphql/supply/SupplyClient.kt | 55 --- .../FileManagementExample.java | 6 +- ...ndboxDataManagementClientUsageExample.java | 8 +- ...ndboxDataManagementClientUsageExample.java | 178 ---------- .../v2/FileManagementExample.java | 26 -- 176 files changed, 522 insertions(+), 4850 deletions(-) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/apache/util/ApacheHttpTransportUtil.kt (92%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt (85%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/authentication/strategy/AuthenticationStrategy.kt (73%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt (89%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/authentication/util/HttpCredentialsAdapterUtil.kt (85%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/ApiClient.kt (78%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/ApiClientApolloHttpEngine.kt (97%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/ApiClientBuilder.kt (89%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/ClientHelpers.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/Environment.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/HttpHandler.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/SdkClient.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/util/ApiClientUtil.kt (68%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/client/util/GsonFactoryUtil.kt (93%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/Credentials.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/configuration/DefaultClientBuilder.kt (98%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/configuration/ExpediaGroupDefaultClientConfiguration.kt (85%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/configuration/FullClientConfiguration.kt (83%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/configuration/SdkMetadata.kt (93%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationCollector.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationProviderQueue.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ConfigurationProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ExpediaGroupConfigurationProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RapidConfigurationProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RuntimeConfigurationProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/SignatureValues.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/http/BlobTypeDetector.kt (92%) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/http/HttpStatus.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/jackson/Config.kt (77%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/jackson/Util.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/ExpediaGroupLogger.kt (92%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/ExpediaGroupLoggerFactory.kt (95%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/LogMessageConstants.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/LogMessageTag.kt (89%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/LoggableContentTypes.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/mask/ExpediaGroupJsonFieldFilter.kt (93%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt (93%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/logging/mask/MaskLogs.kt (98%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/model/Operation.kt (95%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/model/OperationParams.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/model/UserAgent.kt (86%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExceptionUtils.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Hook.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Plugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/PluginConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationHookFactory.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/AuthenticationStrategy.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/ExpediaGroupAuthenticationStrategy.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/RapidAuthenticationStrategy.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLoggerFactory.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LogMasker.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingMaskedFieldsProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestPlugin.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationPlugin.kt rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/Request.kt (98%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/initializer/ApiClientRequestInitializer.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/initializer/InitializerFunctions.kt (70%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/initializer/SdkRequestInitializer.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/interceptor/HttpRequestLoggingInterceptor.kt (89%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/request/interceptor/HttpResponseLoggingInterceptor.kt (89%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/Trait.kt (82%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/authentication/AuthenticationHandlerTrait.kt (72%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/authentication/RefreshAccessTokenTrait.kt (78%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/common/BuilderTrait.kt (68%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/common/ConfigurationTrait.kt (81%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/common/IdTrait.kt (80%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/AuthEndpointTrait.kt (87%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/AuthenticationStrategyTrait.kt (80%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/ClientConfiguration.kt (74%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/ClientConfigurationTrait.kt (67%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/ConnectionTimeoutTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/EndpointTrait.kt (82%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/FullConfigurationTrait.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/KeyTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/MaskedLoggingHeadersTrait.kt (89%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/MaxConnectionsPerRouteTrait.kt (91%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/MaxConnectionsTotalTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/RequestTimeoutTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/SecretTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/SocketTimeoutTrait.kt (90%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/core/trait/configuration/ToClientConfigurationTrait.kt (78%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/client/FileManagementClient.kt (73%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt (76%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/models/GenericError.kt (97%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/models/Upload201Response.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt (91%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt (93%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt (80%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt (83%) rename code/src/main/kotlin/com/expediagroup/sdk/{v2 => }/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt (85%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt rename examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/{v2 => }/LodgingSupplySandboxDataManagementClientUsageExample.java (94%) delete mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java delete mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java diff --git a/code/build.gradle b/code/build.gradle index a0ef0ab3..d4df09a1 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -41,27 +41,25 @@ dependencies { /* Apollo Kotlin */ api 'com.apollographql.apollo:apollo-runtime:4.0.0' implementation 'com.apollographql.adapters:apollo-adapters-core:0.0.4' - implementation 'com.apollographql.ktor:apollo-engine-ktor:0.0.2' /* Apollo Java */ implementation("com.apollographql.java:client:0.0.2") /* EG SDK Core */ implementation("org.apache.tika:tika-core:2.9.2") - implementation("com.google.api-client:google-api-client:2.7.0") + implementation("com.google.api-client:google-api-client:2.7.0") { + exclude group: 'io.grpc', module: 'grpc-api' + exclude group: 'io.grpc', module: 'grpc-context' + + exclude group: 'io.opencensus', module: 'opencensus-api' + exclude group: 'io.opencensus', module: 'opencensus-contrib-http-util' + } implementation("com.ebay.ejmask:ejmask-api:1.0.3") implementation("com.ebay.ejmask:ejmask-extensions:1.0.3") implementation 'org.slf4j:slf4j-simple:2.0.13' - implementation 'io.ktor:ktor-client-core:2.3.10' - implementation 'io.ktor:ktor-client-auth-jvm:2.3.10' - implementation 'io.ktor:ktor-http:2.3.10' - implementation 'io.ktor:ktor-client-okhttp:2.3.10' - implementation 'io.ktor:ktor-client-content-negotiation-jvm:2.3.10' - implementation 'io.ktor:ktor-serialization-jackson-jvm:2.3.10' - implementation 'io.ktor:ktor-client-logging-jvm:2.3.10' - implementation 'io.ktor:ktor-client-encoding:2.3.10' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'org.jetbrains.kotlinx:atomicfu-jvm:0.24.0' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.18.1' implementation 'javax.validation:validation-api:2.0.1.Final' implementation 'org.hibernate.validator:hibernate-validator:6.2.5.Final' implementation 'jakarta.validation:jakarta.validation-api:2.0.2' diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt similarity index 92% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt index f9230d4b..31af39d9 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/apache/util/ApacheHttpTransportUtil.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt @@ -1,9 +1,9 @@ -package com.expediagroup.sdk.v2.core.apache.util +package com.expediagroup.sdk.core.apache.util -import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.core.trait.configuration.MaxConnectionsPerRouteTrait -import com.expediagroup.sdk.v2.core.trait.configuration.MaxConnectionsTotalTrait -import com.expediagroup.sdk.v2.core.trait.configuration.SocketTimeoutTrait +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsPerRouteTrait +import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsTotalTrait +import com.expediagroup.sdk.core.trait.configuration.SocketTimeoutTrait import com.google.api.client.http.apache.v2.ApacheHttpTransport import org.apache.http.client.HttpClient import org.apache.http.client.config.RequestConfig diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt similarity index 85% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt index 07b18c2b..b10c2566 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.authentication.extension +package com.expediagroup.sdk.core.authentication.extension -import com.expediagroup.sdk.v2.core.constant.Authentication +import com.expediagroup.sdk.core.constant.Authentication import com.google.auth.oauth2.OAuth2CredentialsWithRefresh import java.time.Duration diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt similarity index 73% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt index 12393464..d7ba9fc7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/AuthenticationStrategy.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.authentication.strategy +package com.expediagroup.sdk.core.authentication.strategy -import com.expediagroup.sdk.v2.core.trait.authentication.AuthenticationHandlerTrait +import com.expediagroup.sdk.core.trait.authentication.AuthenticationHandlerTrait /** * Enumeration that represents different authentication strategies for a client configuration. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt index 0307d688..fd6fe13b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt @@ -1,16 +1,16 @@ -package com.expediagroup.sdk.v2.core.authentication.strategy +package com.expediagroup.sdk.core.authentication.strategy import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.expediagroup.sdk.v2.core.constant.LoggingMessage -import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.v2.core.logging.LogMessageTag -import com.expediagroup.sdk.v2.core.request.initializer.SdkRequestInitializer -import com.expediagroup.sdk.v2.core.trait.authentication.AuthenticationHandlerTrait -import com.expediagroup.sdk.v2.core.trait.authentication.RefreshAccessTokenTrait -import com.expediagroup.sdk.v2.core.trait.configuration.AuthEndpointTrait -import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.core.trait.configuration.KeyTrait -import com.expediagroup.sdk.v2.core.trait.configuration.SecretTrait +import com.expediagroup.sdk.core.constant.LoggingMessage +import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.core.logging.LogMessageTag +import com.expediagroup.sdk.core.request.initializer.SdkRequestInitializer +import com.expediagroup.sdk.core.trait.authentication.AuthenticationHandlerTrait +import com.expediagroup.sdk.core.trait.authentication.RefreshAccessTokenTrait +import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.configuration.KeyTrait +import com.expediagroup.sdk.core.trait.configuration.SecretTrait import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest import com.google.api.client.auth.oauth2.TokenRequest import com.google.api.client.auth.oauth2.TokenResponse @@ -21,7 +21,7 @@ import com.google.api.client.http.HttpTransport import com.google.api.client.json.gson.GsonFactory import com.google.auth.oauth2.AccessToken import java.time.Instant -import java.util.* +import java.util.Date /** * Factory object for creating Bearer Authentication handlers. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt similarity index 85% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt index 946e5321..0134b410 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/authentication/util/HttpCredentialsAdapterUtil.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt @@ -1,13 +1,17 @@ -package com.expediagroup.sdk.v2.core.authentication.util +package com.expediagroup.sdk.core.authentication.util -import com.expediagroup.sdk.v2.core.apache.util.createApacheHttpTransport -import com.expediagroup.sdk.v2.core.authentication.extension.withDefaultConfigurations -import com.expediagroup.sdk.v2.core.trait.common.IdTrait -import com.expediagroup.sdk.v2.core.trait.configuration.* +import com.expediagroup.sdk.core.apache.util.createApacheHttpTransport +import com.expediagroup.sdk.core.authentication.extension.withDefaultConfigurations +import com.expediagroup.sdk.core.trait.common.IdTrait +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.configuration.KeyTrait +import com.expediagroup.sdk.core.trait.configuration.SecretTrait +import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait +import com.expediagroup.sdk.core.trait.configuration.AuthenticationStrategyTrait import com.google.auth.http.HttpCredentialsAdapter import com.google.auth.oauth2.AccessToken import com.google.auth.oauth2.OAuth2CredentialsWithRefresh -import java.util.* +import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt similarity index 78% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt index 0bdb43fa..a1d5b9ff 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt @@ -1,10 +1,10 @@ -package com.expediagroup.sdk.v2.core.client +package com.expediagroup.sdk.core.client -import com.expediagroup.sdk.v2.core.configuration.ExpediaGroupDefaultClientConfiguration -import com.expediagroup.sdk.v2.core.logging.mask.configureLogMasking -import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.core.trait.configuration.MaskedLoggingBodyFieldsTrait -import com.expediagroup.sdk.v2.core.trait.configuration.MaskedLoggingHeadersTrait +import com.expediagroup.sdk.core.configuration.ExpediaGroupDefaultClientConfiguration +import com.expediagroup.sdk.core.logging.mask.configureLogMasking +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingBodyFieldsTrait +import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingHeadersTrait import com.google.api.client.googleapis.services.AbstractGoogleClient import com.google.api.client.googleapis.services.AbstractGoogleClientRequest diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt similarity index 97% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt index 641f39e9..e8c30b8b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientApolloHttpEngine.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.client +package com.expediagroup.sdk.core.client import com.apollographql.apollo.api.http.HttpHeader import com.apollographql.apollo.api.http.HttpRequest @@ -7,7 +7,7 @@ import com.apollographql.apollo.exception.ApolloNetworkException import com.apollographql.java.client.ApolloDisposable import com.apollographql.java.client.network.http.HttpCallback import com.apollographql.java.client.network.http.HttpEngine -import com.expediagroup.sdk.v2.core.request.Request +import com.expediagroup.sdk.core.request.Request import com.google.api.client.http.HttpHeaders import okio.buffer import okio.source diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt index 30476fd2..5d54e5f4 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/ApiClientBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.client +package com.expediagroup.sdk.core.client -import com.expediagroup.sdk.v2.core.configuration.SdkMetadata -import com.expediagroup.sdk.v2.core.request.initializer.ApiClientRequestInitializer +import com.expediagroup.sdk.core.configuration.SdkMetadata +import com.expediagroup.sdk.core.request.initializer.ApiClientRequestInitializer import com.google.api.client.googleapis.services.AbstractGoogleClient import com.google.api.client.http.GenericUrl import com.google.api.client.http.HttpRequestInitializer diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt deleted file mode 100644 index f7af278b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.configuration.Credentials -import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider -import com.expediagroup.sdk.core.constant.ConfigurationName -import com.expediagroup.sdk.core.constant.Constant -import com.expediagroup.sdk.core.constant.provider.ExceptionMessageProvider.getMissingRequiredConfigurationMessage -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.contract.Contract -import com.expediagroup.sdk.core.contract.adhereTo -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -import com.expediagroup.sdk.core.model.getTransactionId -import com.expediagroup.sdk.core.plugin.Hooks -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationHookFactory -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationPlugin -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy -import com.expediagroup.sdk.core.plugin.encoding.EncodingConfiguration -import com.expediagroup.sdk.core.plugin.encoding.EncodingPlugin -import com.expediagroup.sdk.core.plugin.exception.ExceptionHandlingConfiguration -import com.expediagroup.sdk.core.plugin.exception.ExceptionHandlingPlugin -import com.expediagroup.sdk.core.plugin.hooks -import com.expediagroup.sdk.core.plugin.httptimeout.HttpTimeoutConfiguration -import com.expediagroup.sdk.core.plugin.httptimeout.HttpTimeoutPlugin -import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.core.plugin.logging.LoggingConfiguration -import com.expediagroup.sdk.core.plugin.logging.LoggingPlugin -import com.expediagroup.sdk.core.plugin.plugins -import com.expediagroup.sdk.core.plugin.request.DefaultRequestConfiguration -import com.expediagroup.sdk.core.plugin.request.DefaultRequestPlugin -import io.ktor.client.HttpClient -import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.okhttp.OkHttp -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.request - -val DEFAULT_HTTP_CLIENT_ENGINE: HttpClientEngine = - OkHttp.create { - config { - eventListener(OkHttpEventListener) - } - } - -/** - * The base integration point between the SDK Core and the product SDKs. - */ -abstract class Client( - namespace: String, - environmentProvider: EnvironmentProvider = DefaultEnvironmentProvider(namespace), -) : EnvironmentProvider by environmentProvider { - private val httpHandler = DefaultHttpHandler(environmentProvider) - - companion object { - private val log = ExpediaGroupLoggerFactory.getLogger(this::class.java) - } - - /** The configuration provider to use. */ - abstract val configurationProvider: ConfigurationProvider - - /** The HTTP client to perform requests with. */ - abstract val httpClient: HttpClient - - internal fun buildHttpClient( - configurationProvider: ConfigurationProvider, - authenticationType: AuthenticationStrategy.AuthenticationType, - httpClientEngine: HttpClientEngine = DEFAULT_HTTP_CLIENT_ENGINE, - ): HttpClient = - HttpClient(httpClientEngine) { - val httpClientConfig = this - - val key: String = configurationProvider.key ?: fireMissingConfigurationIssue(ConfigurationName.KEY) - val secret: String = configurationProvider.secret ?: fireMissingConfigurationIssue(ConfigurationName.SECRET) - val endpoint: String = configurationProvider.endpoint ?: fireMissingConfigurationIssue(ConfigurationName.ENDPOINT) - val authEndpoint: String = configurationProvider.authEndpoint ?: fireMissingConfigurationIssue(ConfigurationName.AUTH_ENDPOINT) - val requestTimeout: Long = - configurationProvider.requestTimeout ?: fireMissingConfigurationIssue( - ConfigurationName.REQUEST_TIMEOUT_MILLIS, - ) - val connectionTimeout: Long = - configurationProvider.connectionTimeout ?: fireMissingConfigurationIssue( - ConfigurationName.CONNECTION_TIMEOUT_MILLIS, - ) - val socketTimeout: Long = - configurationProvider.socketTimeout ?: fireMissingConfigurationIssue( - ConfigurationName.SOCKET_TIMEOUT_MILLIS, - ) - val maskedLoggingHeaders: Set = configurationProvider.maskedLoggingHeaders ?: setOf() - val maskedLoggingBodyFields: Set = configurationProvider.maskedLoggingBodyFields ?: setOf() - - val authenticationConfiguration = - AuthenticationConfiguration.from( - httpClientConfig, - Credentials.from(key, secret), - authEndpoint, - authenticationType, - ) - - plugins { - use(LoggingPlugin).with(LoggingConfiguration.from(httpClientConfig, maskedLoggingHeaders, maskedLoggingBodyFields)) - use(AuthenticationPlugin).with(authenticationConfiguration) - use(DefaultRequestPlugin).with(DefaultRequestConfiguration.from(httpClientConfig, endpoint)) - use(EncodingPlugin).with(EncodingConfiguration.from(httpClientConfig)) - use( - HttpTimeoutPlugin, - ).with(HttpTimeoutConfiguration.from(httpClientConfig, requestTimeout, connectionTimeout, socketTimeout)) - use(ExceptionHandlingPlugin).with(ExceptionHandlingConfiguration.from(httpClientConfig)) -// use(SerializationPlugin).with(SerializationConfiguration.from(httpClientConfig)) - } - - hooks { - use(AuthenticationHookFactory).with(authenticationConfiguration) - } - } - - /** Throw an exception if the configuration is missing. */ - private fun fireMissingConfigurationIssue(configurationKey: String): Nothing = - throw ExpediaGroupConfigurationException(getMissingRequiredConfigurationMessage(configurationKey)) - - private fun isNotSuccessfulResponse(response: HttpResponse) = response.status.value !in Constant.SUCCESSFUL_STATUS_CODES_RANGE - - @Suppress("unused") // This is used by the product SDKs. - suspend fun throwIfError(response: HttpResponse, operationId: String) { - if (isNotSuccessfulResponse(response)) { - log.info(LoggingMessageProvider.getResponseUnsuccessfulMessage(response.status, response.request.headers.getTransactionId())) - throwServiceException(response, operationId) - } - } - - abstract suspend fun throwServiceException( - response: HttpResponse, - operationId: String, - ) - - suspend fun performGet(url: String): HttpResponse = httpHandler.performGet(httpClient, url) - - /** - * A [Client] builder. - */ - abstract class Builder> { - /** Sets the API key to use for authentication. */ - protected var key: String? = null - - /** Sets the API secret to use for authentication. */ - protected var secret: String? = null - - /** Sets the API endpoint to use for requests. */ - protected var endpoint: String? = null - - /** - * Sets the request timeout in milliseconds. - * - * Request timeout is the time period from the start of the request to the completion of the response. - * - * Default is infinite - no timeout. - */ - protected var requestTimeout: Long? = null - - /** - * Sets the connection timeout in milliseconds. - * - * Connection timeout is the time period from the start of the request to the establishment of the connection with the server. - * - * Default is 10 seconds (10000 milliseconds). - */ - protected var connectionTimeout: Long? = null - - /** - * Sets the socket timeout in milliseconds. - * - * Socket timeout is the maximum period of inactivity between two consecutive data packets. - * - * Default is 15 seconds (15000 milliseconds). - */ - protected var socketTimeout: Long? = null - - /** Sets tne body fields to be masked in logging. */ - protected var maskedLoggingHeaders: Set? = null - - /** Sets tne body fields to be masked in logging. */ - protected var maskedLoggingBodyFields: Set? = null - - /** Sets the API key to use for authentication. - * - * @param key The API key to use for authentication. - * @return The [Builder] instance. - */ - fun key(key: String): SELF { - this.key = key - return self() - } - - /** Sets the API secret to use for authentication. - * - * @param secret The API secret to use for authentication. - * @return The [Builder] instance. - */ - fun secret(secret: String): SELF { - this.secret = secret - return self() - } - - /** Sets the API endpoint to use for requests. - * - * @param endpoint The API endpoint to use for requests. - * @return The [Builder] instance. - */ - fun endpoint(endpoint: String): SELF { - this.endpoint = endpoint.adhereTo(Contract.TRAILING_SLASH) - log.info(LoggingMessageProvider.getRuntimeConfigurationProviderMessage(ConfigurationName.ENDPOINT, endpoint)) - return self() - } - - /** - * Sets the request timeout in milliseconds. - * Request timeout is the time period from the start of the request to the completion of the response. - * Default is infinite - no timeout. - * - * @param milliseconds The request timeout to be used. - * @return The [Builder] instance. - */ - fun requestTimeout(milliseconds: Long): SELF { - this.requestTimeout = milliseconds - log.info( - LoggingMessageProvider.getRuntimeConfigurationProviderMessage( - ConfigurationName.REQUEST_TIMEOUT_MILLIS, - milliseconds.toString(), - ), - ) - return self() - } - - /** - * Sets the connection timeout in milliseconds. - * Connection timeout is the time period from the start of the request to the establishment of the connection with the server. - * Default is 10 seconds (10000 milliseconds). - * - * @param milliseconds The connection timeout to be used. - * @return The [Builder] instance. - */ - fun connectionTimeout(milliseconds: Long): SELF { - this.connectionTimeout = milliseconds - log.info( - LoggingMessageProvider.getRuntimeConfigurationProviderMessage( - ConfigurationName.CONNECTION_TIMEOUT_MILLIS, - milliseconds.toString(), - ), - ) - return self() - } - - /** - * Sets the socket timeout in milliseconds. - * Socket timeout is the maximum period of inactivity between two consecutive data packets. - * Default is 15 seconds (15000 milliseconds). - * - * @param milliseconds The socket timeout to be used. - * @return The [Builder] instance. - */ - fun socketTimeout(milliseconds: Long): SELF { - this.socketTimeout = milliseconds - log.info( - LoggingMessageProvider.getRuntimeConfigurationProviderMessage( - ConfigurationName.SOCKET_TIMEOUT_MILLIS, - milliseconds.toString(), - ), - ) - return self() - } - - /** - * Sets tne headers to be masked in logging. - * - * @param headers the headers to be masked in logging. - * @return The [Builder] instance. - */ - fun maskedLoggingHeaders(vararg headers: String): SELF { - this.maskedLoggingHeaders = headers.toSet() - log.info( - LoggingMessageProvider.getRuntimeConfigurationProviderMessage( - ConfigurationName.MASKED_LOGGING_HEADERS, - headers.joinToString(), - ), - ) - return self() - } - - /** - * Sets tne body fields to be masked in logging. - * - * @param fields the body fields to be masked in logging. - * @return The [Builder] instance. - */ - fun maskedLoggingBodyFields(vararg fields: String): SELF { - this.maskedLoggingBodyFields = fields.toSet() - log.info( - LoggingMessageProvider.getRuntimeConfigurationProviderMessage( - ConfigurationName.MASKED_LOGGING_BODY_FIELDS, - fields.joinToString(), - ), - ) - return self() - } - - /** Create a [Client] object. */ - abstract fun build(): Client - - @Suppress("UNCHECKED_CAST") // This is safe because of the type parameter - protected open fun self(): SELF = this as SELF - } -} - -/** Executes the hooks for the client. */ -fun T.finalize() = Hooks.execute(this) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ClientHelpers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ClientHelpers.kt deleted file mode 100644 index b2ed3e34..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ClientHelpers.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.client - -/** Handy utils and helpers for a client. */ -abstract class ClientHelpers( - val client: Client -) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/Environment.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Environment.kt deleted file mode 100644 index 4b134f4a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/Environment.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.constant.HeaderKey -import com.expediagroup.sdk.core.model.Properties -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.http.HttpHeaders -import java.util.UUID - -interface EnvironmentProvider { - fun HttpRequestBuilder.appendHeaders() -} - -class DefaultEnvironmentProvider( - namespace: String -) : EnvironmentProvider { - private var properties: Map - - init{ - val propertiesResource = javaClass.classLoader.getResource("sdk.properties") - properties = emptyMap() - } - - private val javaVersion = System.getProperty("java.version") - private val operatingSystemName = System.getProperty("os.name") - private val operatingSystemVersion = System.getProperty("os.version") -// private val userAgent = "expediagroup-sdk-java-$namespace/${properties["sdk-version"]!!} (Java $javaVersion; $operatingSystemName $operatingSystemVersion)" - - override fun HttpRequestBuilder.appendHeaders() { - with(headers) { - append(HttpHeaders.UserAgent, "") - append(HeaderKey.X_SDK_TITLE, "") - append(HeaderKey.TRANSACTION_ID, UUID.randomUUID().toString()) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt deleted file mode 100644 index cf28f895..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.configuration.ExpediaGroupClientConfiguration -import com.expediagroup.sdk.core.configuration.collector.ConfigurationCollector -import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy -import io.ktor.client.HttpClient -import io.ktor.client.engine.HttpClientEngine - -/** - * The integration point between the SDK Core and the product SDKs. - * - * @param httpClientEngine The HTTP client engine to use. - * @param clientConfiguration The configuration for the client. - */ -abstract class ExpediaGroupClient( - namespace: String, - clientConfiguration: ExpediaGroupClientConfiguration, - httpClientEngine: HttpClientEngine = DEFAULT_HTTP_CLIENT_ENGINE -) : Client(namespace) { - private val _configurationProvider: ConfigurationProvider = - ConfigurationCollector.create( - clientConfiguration.toProvider(), - ExpediaGroupConfigurationProvider - ) - private val _httpClient: HttpClient = buildHttpClient(_configurationProvider, AuthenticationStrategy.AuthenticationType.BEARER, httpClientEngine) - - init { - finalize() - } - - override val configurationProvider: ConfigurationProvider - get() = _configurationProvider - - override val httpClient: HttpClient - get() = _httpClient - - /** An [ExpediaGroupClient] builder. */ - @Suppress("unused") // This is used by the generated SDK clients. - abstract class Builder> : Client.Builder() { - /** Sets the API auth endpoint to use for requests. */ - protected var authEndpoint: String? = null - - /** Sets the API auth endpoint to use for requests. - * - * @param authEndpoint The API auth endpoint to use for requests. - * @return The [Builder] instance. - */ - fun authEndpoint(authEndpoint: String): SELF { - this.authEndpoint = authEndpoint - return self() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/HttpHandler.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/HttpHandler.kt deleted file mode 100644 index 548fad03..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/HttpHandler.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.client - -import io.ktor.client.HttpClient -import io.ktor.client.request.request -import io.ktor.client.request.url -import io.ktor.client.statement.HttpResponse -import io.ktor.http.HttpMethod - -internal interface HttpHandler { - suspend fun performGet( - httpClient: HttpClient, - link: String - ): HttpResponse -} - -internal class DefaultHttpHandler( - private val environmentProvider: EnvironmentProvider -) : HttpHandler, EnvironmentProvider by environmentProvider { - override suspend fun performGet( - httpClient: HttpClient, - link: String - ): HttpResponse { - return httpClient.request { - method = HttpMethod.Get - url(link) - appendHeaders() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt deleted file mode 100644 index 25dbd741..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.constant.HeaderKey -import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory -import okhttp3.Call -import okhttp3.Connection -import okhttp3.EventListener -import okhttp3.Handshake -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import java.io.IOException -import java.net.InetSocketAddress -import java.net.Proxy - -object OkHttpEventListener : EventListener() { - private val log = ExpediaGroupLoggerFactory.getLogger(this::class.java) - - fun Call.getTransactionId() = request().headers[HeaderKey.TRANSACTION_ID] - - override fun callStart(call: Call) { - super.callStart(call) - log.debug("Call start for transaction-id: [${call.getTransactionId()}]") - } - - override fun callEnd(call: Call) { - super.callEnd(call) - log.debug("Call end for transaction-id: [${call.getTransactionId()}]") - } - - override fun callFailed( - call: Call, - ioe: IOException - ) { - super.callFailed(call, ioe) - log.debug("Call failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") - } - - override fun canceled(call: Call) { - super.canceled(call) - log.debug("Call canceled for transaction-id: [${call.getTransactionId()}]") - } - - override fun connectStart( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy - ) { - super.connectStart(call, inetSocketAddress, proxy) - log.debug("Connect start for transaction-id: [${call.getTransactionId()}]") - } - - override fun connectEnd( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy, - protocol: Protocol? - ) { - super.connectEnd(call, inetSocketAddress, proxy, protocol) - log.debug("Connect end for transaction-id: [${call.getTransactionId()}]") - } - - override fun connectFailed( - call: Call, - inetSocketAddress: InetSocketAddress, - proxy: Proxy, - protocol: Protocol?, - ioe: IOException - ) { - super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe) - log.debug("Connect failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") - } - - override fun connectionAcquired( - call: Call, - connection: Connection - ) { - super.connectionAcquired(call, connection) - log.debug("Connection acquired for transaction-id: [${call.getTransactionId()}]") - } - - override fun connectionReleased( - call: Call, - connection: Connection - ) { - super.connectionReleased(call, connection) - log.debug("Connection released for transaction-id: [${call.getTransactionId()}]") - } - - override fun secureConnectStart(call: Call) { - super.secureConnectStart(call) - log.debug("Secure connect start for transaction-id: [${call.getTransactionId()}]") - } - - override fun secureConnectEnd( - call: Call, - handshake: Handshake? - ) { - super.secureConnectEnd(call, handshake) - log.debug("Secure connect end for transaction-id: [${call.getTransactionId()}]") - } - - override fun requestHeadersStart(call: Call) { - super.requestHeadersStart(call) - log.debug("Sending request headers start for transaction-id: [${call.getTransactionId()}]") - } - - override fun requestHeadersEnd( - call: Call, - request: Request - ) { - super.requestHeadersEnd(call, request) - log.debug("Sending request headers end for transaction-id: [${call.getTransactionId()}]") - } - - override fun requestBodyStart(call: Call) { - super.requestBodyStart(call) - log.debug("Sending request body start for transaction-id: [${call.getTransactionId()}]") - } - - override fun requestBodyEnd( - call: Call, - byteCount: Long - ) { - super.requestBodyEnd(call, byteCount) - log.debug("Sending request body end for transaction-id: [${call.getTransactionId()}] with byte count: $byteCount") - } - - override fun requestFailed( - call: Call, - ioe: IOException - ) { - super.requestFailed(call, ioe) - log.debug("Request failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") - } - - override fun responseHeadersStart(call: Call) { - super.responseHeadersStart(call) - log.debug("Receiving response headers start for transaction-id: [${call.getTransactionId()}]") - } - - override fun responseHeadersEnd( - call: Call, - response: Response - ) { - super.responseHeadersEnd(call, response) - log.debug("Receiving response headers end for transaction-id: [${call.getTransactionId()}]") - } - - override fun responseBodyStart(call: Call) { - super.responseBodyStart(call) - log.debug("Receiving response body start for transaction-id: [${call.getTransactionId()}]") - } - - override fun responseBodyEnd( - call: Call, - byteCount: Long - ) { - super.responseBodyEnd(call, byteCount) - log.debug("Receiving response body end for transaction-id: [${call.getTransactionId()}] with byte count: $byteCount") - } - - override fun responseFailed( - call: Call, - ioe: IOException - ) { - super.responseFailed(call, ioe) - log.debug("Receiving response failed for transaction-id: [${call.getTransactionId()}] with exception message: ${ioe.message}") - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt index b766ef6c..fd5026c0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/SdkClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt @@ -1,10 +1,10 @@ -package com.expediagroup.sdk.v2.core.client +package com.expediagroup.sdk.core.client -import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration -import com.expediagroup.sdk.v2.core.request.Request -import com.expediagroup.sdk.v2.core.client.util.createApiClient -import com.expediagroup.sdk.v2.core.jackson.deserialize -import com.expediagroup.sdk.v2.core.model.Operation +import com.expediagroup.sdk.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.core.request.Request +import com.expediagroup.sdk.core.client.util.createApiClient +import com.expediagroup.sdk.core.jackson.deserialize +import com.expediagroup.sdk.core.model.Operation import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import java.io.InputStream diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt similarity index 68% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt index 9f3a1c32..cd8c75eb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/ApiClientUtil.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt @@ -1,12 +1,12 @@ -package com.expediagroup.sdk.v2.core.client.util +package com.expediagroup.sdk.core.client.util -import com.expediagroup.sdk.v2.core.apache.util.getSingletonApacheHttpTransport -import com.expediagroup.sdk.v2.core.authentication.util.getHttpCredentialsAdapter -import com.expediagroup.sdk.v2.core.client.ApiClient -import com.expediagroup.sdk.v2.core.client.ApiClientBuilder -import com.expediagroup.sdk.v2.core.request.initializer.SdkRequestInitializer -import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.core.trait.configuration.EndpointTrait +import com.expediagroup.sdk.core.apache.util.getSingletonApacheHttpTransport +import com.expediagroup.sdk.core.authentication.util.getHttpCredentialsAdapter +import com.expediagroup.sdk.core.client.ApiClient +import com.expediagroup.sdk.core.client.ApiClientBuilder +import com.expediagroup.sdk.core.request.initializer.SdkRequestInitializer +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.configuration.EndpointTrait import com.google.api.client.http.GenericUrl import com.google.api.client.http.HttpTransport diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt similarity index 93% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt index 5fc526c1..99fca2b8 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/client/util/GsonFactoryUtil.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.client.util +package com.expediagroup.sdk.core.client.util import com.google.api.client.json.gson.GsonFactory diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt deleted file mode 100644 index d8dfdedd..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration - -import com.expediagroup.sdk.core.configuration.provider.RuntimeConfigurationProvider - -interface ClientConfiguration { - val key: String? - val secret: String? - val endpoint: String? - val requestTimeout: Long? - val connectionTimeout: Long? - val socketTimeout: Long? - val maskedLoggingHeaders: Set? - val maskedLoggingBodyFields: Set? - - /** Build a [RuntimeConfigurationProvider] from a [ClientConfiguration]. */ - fun toProvider(): RuntimeConfigurationProvider = - RuntimeConfigurationProvider( - key = key, - secret = secret, - endpoint = endpoint, - requestTimeout = requestTimeout, - connectionTimeout = connectionTimeout, - socketTimeout = socketTimeout, - maskedLoggingHeaders = maskedLoggingHeaders, - maskedLoggingBodyFields = maskedLoggingBodyFields - ) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/Credentials.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/Credentials.kt deleted file mode 100644 index 1e249b81..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/Credentials.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration - -/** - * A pair of key-secret. - * - * @property key the client key - * @property secret the client secret - */ -internal data class Credentials( - val key: String, - val secret: String -) { - /** - * A factory of [Credentials]. - */ - companion object Factory { - /** - * Create a [Credentials] object. - * - * @param key Client key. - * @param secret Client secret. - * @return ClientCredentials object. - */ - @JvmStatic - fun from( - key: String, - secret: String - ): Credentials = Credentials(key, secret) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt index e7f82c0e..42aee5e0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/DefaultClientBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.configuration +package com.expediagroup.sdk.core.configuration /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt deleted file mode 100644 index e5289d9e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration - -import com.expediagroup.sdk.core.client.ExpediaGroupClient -import com.expediagroup.sdk.core.configuration.provider.RuntimeConfigurationProvider - -/** - * Configuration for the [ExpediaGroupClient]. - * - * @property key The API key to use for authentication. - * @property secret The API secret to use for authentication. - * @property endpoint The API endpoint to use for requests. - * @property requestTimeout The request timeout to be used in milliseconds. - * @property connectionTimeout The connection timeout to be used in milliseconds. - * @property socketTimeout The socket timeout to be used in milliseconds. - * @property maskedLoggingHeaders The headers to be masked in logging. - * @property maskedLoggingBodyFields The body fields to be masked in logging. - * @property authEndpoint The API endpoint to use for authentication. - */ -data class ExpediaGroupClientConfiguration( - override val key: String? = null, - override val secret: String? = null, - override val endpoint: String? = null, - override val requestTimeout: Long? = null, - override val connectionTimeout: Long? = null, - override val socketTimeout: Long? = null, - override val maskedLoggingHeaders: Set? = null, - override val maskedLoggingBodyFields: Set? = null, - val authEndpoint: String? = null -) : ClientConfiguration { - /** Build a [RuntimeConfigurationProvider] from an [ExpediaGroupClientConfiguration]. */ - override fun toProvider(): RuntimeConfigurationProvider = super.toProvider().copy(authEndpoint = authEndpoint) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt similarity index 85% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt index bd0b4764..4f01e3a8 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/ExpediaGroupDefaultClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.configuration +package com.expediagroup.sdk.core.configuration -import com.expediagroup.sdk.v2.core.constant.Constant -import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.core.constant.Constant +import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy /** * Implementation of `FullClientConfiguration` that provides default configuration values for the Expedia Group API client. @@ -23,7 +23,7 @@ object ExpediaGroupDefaultClientConfiguration : override fun getEndpoint(): String = "https://api.expediagroup.com/" override fun getAuthEndpoint(): String = "${getEndpoint()}identity/oauth2/v3/token/" override fun getAuthenticationStrategy(): AuthenticationStrategy = AuthenticationStrategy.BEARER - override fun getRequestTimeout(): Long = Constant.INFINITE_TIMEOUT + override fun getRequestTimeout(): Long = Constant.ONE_HOUR_IN_MILLIS override fun getConnectionTimeout(): Long = Constant.TEN_SECONDS_IN_MILLIS override fun getSocketTimeout(): Long = Constant.FIFTEEN_SECONDS_IN_MILLIS override fun getMaxConnectionsTotal(): Int = Constant.MAX_CONNECTIONS_TOTAL diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt similarity index 83% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt index a200b1f7..f8c8e081 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/FullClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt @@ -1,10 +1,21 @@ -package com.expediagroup.sdk.v2.core.configuration +package com.expediagroup.sdk.core.configuration import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy -import com.expediagroup.sdk.v2.core.trait.common.BuilderTrait -import com.expediagroup.sdk.v2.core.trait.configuration.* -import java.util.* +import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.core.trait.common.BuilderTrait +import com.expediagroup.sdk.core.trait.configuration.KeyTrait +import com.expediagroup.sdk.core.trait.configuration.SecretTrait +import com.expediagroup.sdk.core.trait.configuration.EndpointTrait +import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait +import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingHeadersTrait +import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingBodyFieldsTrait +import com.expediagroup.sdk.core.trait.configuration.RequestTimeoutTrait +import com.expediagroup.sdk.core.trait.configuration.SocketTimeoutTrait +import com.expediagroup.sdk.core.trait.configuration.ConnectionTimeoutTrait +import com.expediagroup.sdk.core.trait.configuration.AuthenticationStrategyTrait +import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsTotalTrait +import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsPerRouteTrait +import java.util.UUID /** * Interface representing the full configuration required for a client setup. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt similarity index 93% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt index b1cd4b13..04841748 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/configuration/SdkMetadata.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.configuration +package com.expediagroup.sdk.core.configuration import java.util.jar.Manifest diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationCollector.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationCollector.kt deleted file mode 100644 index a83d40fb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationCollector.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.collector - -import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider -import com.expediagroup.sdk.core.constant.ConfigurationName.AUTH_ENDPOINT -import com.expediagroup.sdk.core.constant.ConfigurationName.CONFIGURATION_COLLECTOR -import com.expediagroup.sdk.core.constant.ConfigurationName.CONNECTION_TIMEOUT_MILLIS -import com.expediagroup.sdk.core.constant.ConfigurationName.ENDPOINT -import com.expediagroup.sdk.core.constant.ConfigurationName.KEY -import com.expediagroup.sdk.core.constant.ConfigurationName.MASKED_LOGGING_BODY_FIELDS -import com.expediagroup.sdk.core.constant.ConfigurationName.MASKED_LOGGING_HEADERS -import com.expediagroup.sdk.core.constant.ConfigurationName.REQUEST_TIMEOUT_MILLIS -import com.expediagroup.sdk.core.constant.ConfigurationName.SECRET -import com.expediagroup.sdk.core.constant.ConfigurationName.SOCKET_TIMEOUT_MILLIS -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory - -/** - * Configuration collector that collects configuration from all available providers. - * - * @param providers A configuration providers queue. - */ -internal class ConfigurationCollector private constructor(providers: ConfigurationProviderQueue) : ConfigurationProvider { - override val name: String = CONFIGURATION_COLLECTOR - - companion object Factory { - private val log = ExpediaGroupLoggerFactory.getLogger(ConfigurationCollector::class.java) - - /** - * Creates a new [ConfigurationCollector] with the given [providerQueue]. - * - * @param providerQueue the [ConfigurationProviderQueue] to use. - * @return a new [ConfigurationCollector] with the given [providerQueue]. - */ - fun create(providerQueue: ConfigurationProviderQueue): ConfigurationCollector = ConfigurationCollector(providerQueue) - - /** - * Creates a new [ConfigurationCollector] with the given [providers]. - * - * @param providers the [ConfigurationProvider]s to use. - * @return a new [ConfigurationCollector] with the given [providers]. - */ - fun create(vararg providers: ConfigurationProvider): ConfigurationCollector = create(ConfigurationProviderQueue.from(providers.asList())) - } - - override val key: String? = providers.firstWith { it.key }.also { it?.log(KEY) }?.retrieve() - override val secret: String? = providers.firstWith { it.secret }.also { it?.log(SECRET) }?.retrieve() - override val endpoint: String? = providers.firstWith { it.endpoint }.also { it?.log(ENDPOINT) }?.retrieve() - override val authEndpoint: String? = providers.firstWith { it.authEndpoint }.also { it?.log(AUTH_ENDPOINT) }?.retrieve() - override val requestTimeout: Long? = providers.firstWith { it.requestTimeout }.also { it?.log(REQUEST_TIMEOUT_MILLIS) }?.retrieve() - override val connectionTimeout: Long? = providers.firstWith { it.connectionTimeout }.also { it?.log(CONNECTION_TIMEOUT_MILLIS) }?.retrieve() - override val socketTimeout: Long? = providers.firstWith { it.socketTimeout }.also { it?.log(SOCKET_TIMEOUT_MILLIS) }?.retrieve() - override val maskedLoggingHeaders: Set? = providers.firstWith { it.maskedLoggingHeaders }.also { it?.log(MASKED_LOGGING_HEADERS) }?.retrieve() - override val maskedLoggingBodyFields: Set? = providers.firstWith { it.maskedLoggingBodyFields }.also { it?.log(MASKED_LOGGING_BODY_FIELDS) }?.retrieve() - - private fun ProvidedConfiguration.log(configurationName: String) { - log.info(LoggingMessageProvider.getChosenProviderMessage(configurationName, providerName)) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationProviderQueue.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationProviderQueue.kt deleted file mode 100644 index 3a964739..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationProviderQueue.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.collector - -import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider - -/** - * A queue of all configuration providers. - * - * @property providers List of configuration providers - */ -internal class ConfigurationProviderQueue private constructor(private val providers: List) { - /** Returns the first provider in the queue. */ - fun first(): ConfigurationProvider? = providers.firstOrNull() - - /** Returns the first provider in the queue that matches the given [predicate]. */ - fun first(predicate: (ConfigurationProvider) -> Boolean): ConfigurationProvider? = providers.firstOrNull(predicate) - - /** Returns the first provider in the queue that matches the given [predicate] if found, null otherwise.*/ - fun firstWith(predicate: (provider: ConfigurationProvider) -> T?): ProvidedConfiguration? = first { predicate(it) != null }?.let { ProvidedConfiguration(predicate(it)!!, it.name) } - - companion object { - /** Builds a [ConfigurationProviderQueue] from the given [providers]. - * - * @param providers the providers to build the queue from. - * @return a [ConfigurationProviderQueue] instance. - */ - fun from(providers: List) = ConfigurationProviderQueue(providers.toList()) - } -} - -internal data class ProvidedConfiguration(private val configuration: T, val providerName: String) { - fun retrieve(): T = configuration -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ConfigurationProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ConfigurationProvider.kt deleted file mode 100644 index ac7f7ea4..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ConfigurationProvider.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.provider - -import com.expediagroup.sdk.core.constant.Constant - -/** - * A configuration provider that can be used to provide configuration values. - */ -interface ConfigurationProvider { - /** The name of the provider. */ - val name: String - - /** The API key to use for authentication. */ - val key: String? - get() = Constant.EMPTY_STRING - - /** The API secret to use for authentication. */ - val secret: String? - get() = Constant.EMPTY_STRING - - /** The API endpoint to use for requests. */ - val endpoint: String? - - /** The API endpoint to use for authentication. */ - val authEndpoint: String? - get() = Constant.EMPTY_STRING - - /** The time period from the start of the request to the completion of the response. */ - val requestTimeout: Long? - - /** The time period from the start of the request to the establishment of the connection with the server. */ - val connectionTimeout: Long? - - /** The maximum period of inactivity between two consecutive data packets. */ - val socketTimeout: Long? - - /** The headers to be masked in logging. */ - val maskedLoggingHeaders: Set? - get() = setOf() - - /** The body fields to be masked in logging.*/ - val maskedLoggingBodyFields: Set? - get() = setOf() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ExpediaGroupConfigurationProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ExpediaGroupConfigurationProvider.kt deleted file mode 100644 index 75295aeb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ExpediaGroupConfigurationProvider.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.provider - -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.authEndpoint -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.connectionTimeout -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.endpoint -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.name -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.requestTimeout -import com.expediagroup.sdk.core.configuration.provider.ExpediaGroupConfigurationProvider.socketTimeout -import com.expediagroup.sdk.core.constant.Constant - -/** - * Default configuration provider for ExpediaGroup. - * - * @property name The name of the provider. - * @property endpoint The API endpoint to use for requests. - * @property authEndpoint The API endpoint to use for authentication. - * @property requestTimeout The API response timeout to use for requests. - * @property connectionTimeout The connection timeout to be used in milliseconds. - * @property socketTimeout The socket timeout to be used in milliseconds. - */ -internal object ExpediaGroupConfigurationProvider : ConfigurationProvider { - override val name: String = "ExpediaGroup Configuration Provider" - override val endpoint: String = "https://api.expediagroup.com/" - override val authEndpoint: String = "${endpoint}identity/oauth2/v3/token/" - override val requestTimeout: Long = Constant.INFINITE_TIMEOUT - override val connectionTimeout: Long = Constant.TEN_SECONDS_IN_MILLIS - override val socketTimeout: Long = Constant.FIFTEEN_SECONDS_IN_MILLIS -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RapidConfigurationProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RapidConfigurationProvider.kt deleted file mode 100644 index bc8e06c6..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RapidConfigurationProvider.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.provider - -import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.connectionTimeout -import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.endpoint -import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.name -import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.requestTimeout -import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider.socketTimeout -import com.expediagroup.sdk.core.constant.Constant - -/** - * Default configuration provider for Rapid. - * - * @property name The name of the provider. - * @property endpoint The API endpoint to use for requests. - * @property requestTimeout The API response timeout to use for requests. - * @property connectionTimeout The connection timeout to use for requests. - * @property socketTimeout The socket timeout to use for requests. - */ -internal object RapidConfigurationProvider : ConfigurationProvider { - override val name: String = "Rapid Configuration Provider" - override val endpoint: String = "https://api.ean.com/v3" - override val requestTimeout: Long = Constant.INFINITE_TIMEOUT - override val connectionTimeout: Long = Constant.TEN_SECONDS_IN_MILLIS - override val socketTimeout: Long = Constant.FIFTEEN_SECONDS_IN_MILLIS -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RuntimeConfigurationProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RuntimeConfigurationProvider.kt deleted file mode 100644 index fd72dc58..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RuntimeConfigurationProvider.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.configuration.provider - -import com.expediagroup.sdk.core.constant.ConfigurationName.RUNTIME_CONFIGURATION_PROVIDER - -/** - * A runtime-built configuration provider. - * - * @property name The name of the provider. - * @property key The API key to use for authentication. - * @property secret The API secret to use for authentication. - * @property endpoint The API endpoint to use for requests. - * @property authEndpoint The API endpoint to use for authentication. - * @property requestTimeout The request timeout to be used in milliseconds. - * @property connectionTimeout The connection timeout to be used in milliseconds. - * @property socketTimeout The socket timeout to be used in milliseconds. - * @property maskedLoggingHeaders The headers to be masked in logging. - * @property maskedLoggingBodyFields The body fields to be masked in logging. - */ -data class RuntimeConfigurationProvider( - override val name: String = RUNTIME_CONFIGURATION_PROVIDER, - override val key: String? = null, - override val secret: String? = null, - override val endpoint: String? = null, - override val authEndpoint: String? = null, - override val requestTimeout: Long? = null, - override val connectionTimeout: Long? = null, - override val socketTimeout: Long? = null, - override val maskedLoggingHeaders: Set? = null, - override val maskedLoggingBodyFields: Set? = null -) : ConfigurationProvider diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt index 2b22a50e..ed3d676e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt @@ -1,29 +1,5 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.expediagroup.sdk.core.constant -internal object Authentication { - const val AUTHORIZATION_REQUEST_LOCK_DELAY = 20L - const val BEARER_EXPIRY_DATE_MARGIN: Long = 10 // In seconds - - const val EAN = "EAN" - - const val BEARER = "Bearer" - - const val GRANT_TYPE = "grant_type" - - const val CLIENT_CREDENTIALS = "client_credentials" +object Authentication { + const val BEARER_EXPIRY_MARGIN_IN_SECONDS: Long = 10 } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt deleted file mode 100644 index 0004086b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object ConfigurationName { - const val KEY = "key" - - const val SECRET = "secret" - - const val ENDPOINT = "endpoint" - - const val AUTH_ENDPOINT = "auth endpoint" - - const val REQUEST_TIMEOUT_MILLIS = "request timeout in milliseconds" - - const val CONNECTION_TIMEOUT_MILLIS = "connection timeout in milliseconds" - - const val SOCKET_TIMEOUT_MILLIS = "socket timeout in milliseconds" - - const val MASKED_LOGGING_HEADERS = "masked logging headers" - - const val MASKED_LOGGING_BODY_FIELDS = "masked logging body fields" - - const val RUNTIME_CONFIGURATION_PROVIDER = "runtime configuration" - - const val CONFIGURATION_COLLECTOR = "configuration collector" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt index 5f7a9d8c..c64e10af 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt @@ -15,15 +15,18 @@ */ package com.expediagroup.sdk.core.constant -import io.ktor.client.plugins.HttpTimeout - internal object Constant { - const val EMPTY_STRING = "" - const val TEN_SECONDS_IN_MILLIS = 10_000L - const val FIFTEEN_SECONDS_IN_MILLIS = 15_000L - const val INFINITE_TIMEOUT = HttpTimeout.INFINITE_TIMEOUT_MS + const val NEWLINE = "\n" + const val COMMA_SPACE = ", " + const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" + const val TEN_SECONDS_IN_MILLIS = 10_0000L + const val FIFTEEN_SECONDS_IN_MILLIS = 150_000L + const val ONE_HOUR_IN_MILLIS = 3_600_000L private const val SUCCESSFUL_STATUS_CODES_RANGE_START = 200 private const val SUCCESSFUL_STATUS_CODES_RANGE_END = 299 val SUCCESSFUL_STATUS_CODES_RANGE: IntRange = SUCCESSFUL_STATUS_CODES_RANGE_START..SUCCESSFUL_STATUS_CODES_RANGE_END + + const val MAX_CONNECTIONS_TOTAL: Int = 500 + const val MAX_CONNECTIONS_PER_ROUTE: Int = 100 } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt index f6668134..57ab1552 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt @@ -15,10 +15,10 @@ */ package com.expediagroup.sdk.core.constant -import io.ktor.http.HttpHeaders +import com.google.auth.http.AuthHttpConstants internal data object LogMaskingFields { - val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(HttpHeaders.Authorization) + val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(AuthHttpConstants.AUTHORIZATION) val DEFAULT_MASKED_BODY_FIELDS: Set = setOf( "cvv", diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.kt deleted file mode 100644 index 63d51c0b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object LogMaskingRegex { - val FIELD_REGEX = "^[a-zA-Z0-9-_]+$".toRegex() - - val NUMBER_FIELD_REGEX = "(?<=[\"']?number[\"']?:\\s?[\"'])(\\s*\\d{15,16}\\s*)(?=[\"'])".toRegex() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt index 50d9b3bc..00c25226 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt @@ -22,11 +22,7 @@ internal object LoggingMessage { const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" - const val TOKEN_CLEARING_IN_PROGRESS = "Clearing tokens" - - const val TOKEN_CLEARING_SUCCESSFUL = "Tokens successfully cleared" - - const val TOKEN_EXPIRED = "Token expired or is about to expire. Request will wait until token is renewed" + const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" const val OMITTED = "<-- omitted -->" } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/SignatureValues.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/SignatureValues.kt deleted file mode 100644 index cdf04e37..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/SignatureValues.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object SignatureValues { - const val ONE_BYTE_MASK = 0xFF - - const val INCREMENT = 0x100 - - const val RADIX = 16 - - const val API_KEY = "apikey" - - const val SIGNATURE = "signature" - - const val TIMESTAMP = "timestamp" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.kt deleted file mode 100644 index 475e6f4c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant.provider - -internal object LogMaskingRegexProvider { - fun getMaskedFieldsRegex(maskedBodyFields: Set) = "(?<=[\"']?(${maskedBodyFields.joinToString("|")})[\"']?:\\s?[\"'])(\\s*[^\"']+\\s*)(?=[\"'])".toRegex() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt index f9d358ad..a1db51eb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt @@ -15,13 +15,14 @@ */ package com.expediagroup.sdk.core.constant.provider -import io.ktor.http.HttpStatusCode +import com.expediagroup.sdk.core.http.HttpStatus + internal object LoggingMessageProvider { fun getTokenExpiresInMessage(expiresIn: Int) = "New token expires in $expiresIn seconds" fun getResponseUnsuccessfulMessage( - httpStatusCode: HttpStatusCode, + httpStatusCode: HttpStatus, transactionId: String? ) = "Unsuccessful response [$httpStatusCode]${getTransactionIdMessage(transactionId)}" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.kt deleted file mode 100644 index 6527759c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.contract - -internal typealias Operation = (String) -> String - -/** - * A contract for a specific [operation]. - * - * @property operation The operation to perform on a string. - */ -internal enum class Contract(val operation: Operation) { - TRAILING_SLASH({ if (it.endsWith("/")) it else "$it/" }) -} - -/** - * Adheres to the given [contract] on a [String]. - * - * @param contract the [Contract] to adhere to. - * @return the [String] adhering to the given [contract]. - */ -internal fun String.adhereTo(contract: Contract): String = contract.operation(this) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt similarity index 92% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt index 8c4478d7..ec873abf 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/http/BlobTypeDetector.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.http +package com.expediagroup.sdk.core.http import org.apache.tika.Tika diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/HttpStatus.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/HttpStatus.kt new file mode 100644 index 00000000..893a588f --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/HttpStatus.kt @@ -0,0 +1,93 @@ +package com.expediagroup.sdk.core.http + +import com.google.api.client.http.HttpStatusCodes + +enum class HttpStatus(val code: Int) { + // Informational responses (100–199) + CONTINUE(100), + SWITCHING_PROTOCOLS(101), + PROCESSING(102), + EARLY_HINTS(103), + + // Successful responses (200–299) + OK(200), + CREATED(201), + ACCEPTED(202), + NON_AUTHORITATIVE_INFORMATION(203), + NO_CONTENT(204), + RESET_CONTENT(205), + PARTIAL_CONTENT(206), + MULTI_STATUS(207), + ALREADY_REPORTED(208), + IM_USED(226), + + // Redirection messages (300–399) + MULTIPLE_CHOICES(300), + MOVED_PERMANENTLY(301), + FOUND(302), + SEE_OTHER(303), + NOT_MODIFIED(304), + USE_PROXY(305), + TEMPORARY_REDIRECT(307), + PERMANENT_REDIRECT(308), + + // Client error responses (400–499) + BAD_REQUEST(400), + UNAUTHORIZED(401), + PAYMENT_REQUIRED(402), + FORBIDDEN(403), + NOT_FOUND(404), + METHOD_NOT_ALLOWED(405), + NOT_ACCEPTABLE(406), + PROXY_AUTHENTICATION_REQUIRED(407), + REQUEST_TIMEOUT(408), + CONFLICT(409), + GONE(410), + LENGTH_REQUIRED(411), + PRECONDITION_FAILED(412), + PAYLOAD_TOO_LARGE(413), + URI_TOO_LONG(414), + UNSUPPORTED_MEDIA_TYPE(415), + RANGE_NOT_SATISFIABLE(416), + EXPECTATION_FAILED(417), + IM_A_TEAPOT(418), + MISDIRECTED_REQUEST(421), + UNPROCESSABLE_ENTITY(422), + LOCKED(423), + FAILED_DEPENDENCY(424), + TOO_EARLY(425), + UPGRADE_REQUIRED(426), + PRECONDITION_REQUIRED(428), + TOO_MANY_REQUESTS(429), + REQUEST_HEADER_FIELDS_TOO_LARGE(431), + UNAVAILABLE_FOR_LEGAL_REASONS(451), + + // Server error responses (500–599) + INTERNAL_SERVER_ERROR(500), + NOT_IMPLEMENTED(501), + BAD_GATEWAY(502), + SERVICE_UNAVAILABLE(503), + GATEWAY_TIMEOUT(504), + HTTP_VERSION_NOT_SUPPORTED(505), + VARIANT_ALSO_NEGOTIATES(506), + INSUFFICIENT_STORAGE(507), + LOOP_DETECTED(508), + NOT_EXTENDED(510), + NETWORK_AUTHENTICATION_REQUIRED(511), + + // Non-standard status codes (e.g., Apache) + THIS_IS_FINE(218); // Non-standard code, used by some Apache modules + + fun isSuccess(): Boolean = + HttpStatusCodes.isSuccess(code) + + companion object { + fun fromCode(code: Int): HttpStatus { + entries.find { it.code == code }?.let { + return it + } + + throw IllegalArgumentException("Invalid status code: $code") + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Config.kt similarity index 77% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Config.kt index 665048ad..fda8606e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Config.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Config.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.jackson +package com.expediagroup.sdk.core.jackson import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Util.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Util.kt index 13462d62..0949ddbb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/jackson/Util.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Util.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.jackson +package com.expediagroup.sdk.core.jackson import com.fasterxml.jackson.core.type.TypeReference diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt similarity index 92% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt index 9d94bf4b..f8dc6fc1 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.expediagroup.sdk.v2.core.logging +package com.expediagroup.sdk.core.logging -import com.expediagroup.sdk.v2.core.constant.Constant -import com.expediagroup.sdk.v2.core.constant.LoggingMessage.LOGGING_PREFIX -import com.expediagroup.sdk.v2.core.logging.mask.maskLogs +import com.expediagroup.sdk.core.constant.Constant +import com.expediagroup.sdk.core.constant.LoggingMessage.LOGGING_PREFIX +import com.expediagroup.sdk.core.logging.mask.maskLogs import org.slf4j.Logger /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt similarity index 95% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt index 7e1d3408..56881abf 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/ExpediaGroupLoggerFactory.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.expediagroup.sdk.v2.core.logging +package com.expediagroup.sdk.core.logging import org.slf4j.LoggerFactory diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt index d23615c4..408aab63 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageConstants.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging +package com.expediagroup.sdk.core.logging /** * Object holding constants for logging messages related to HTTP requests and responses. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt index ae631024..6c11f15a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LogMessageTag.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging +package com.expediagroup.sdk.core.logging /** * Enumeration representing the different tags available for log messages. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt index c1f040b3..bac40449 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/LoggableContentTypes.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging +package com.expediagroup.sdk.core.logging import org.apache.http.entity.ContentType diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt similarity index 93% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt index 0c0288b4..aabdfbc7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldFilter.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging.mask +package com.expediagroup.sdk.core.logging.mask import com.ebay.ejmask.core.BaseFilter diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt similarity index 93% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt index 5d10eb30..6315aa01 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging.mask +package com.expediagroup.sdk.core.logging.mask import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt index 062a2131..e108a587 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/logging/mask/MaskLogs.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.logging.mask +package com.expediagroup.sdk.core.logging.mask import com.ebay.ejmask.core.BaseFilter import com.ebay.ejmask.core.EJMask diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.kt deleted file mode 100644 index 47c14c2e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -import com.expediagroup.sdk.core.constant.HeaderKey -import io.ktor.http.Headers -import io.ktor.http.HeadersBuilder - -internal fun Headers.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) - -internal fun HeadersBuilder.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt similarity index 95% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt index 5879f1e7..4da095cb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Operation.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.expediagroup.sdk.v2.core.model +package com.expediagroup.sdk.core.model import com.google.api.client.http.HttpContent import com.expediagroup.sdk.core.model.TransactionId diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt index ee296b99..be61cace 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/OperationParams.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.expediagroup.sdk.v2.core.model +package com.expediagroup.sdk.core.model interface OperationParams { fun getHeaders(): Map? diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt index daceba63..c024d35a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt @@ -27,13 +27,17 @@ import kotlin.collections.Map.Entry * @property body The body of the response * @property headers The headers of the response */ -@Suppress("MemberVisibilityCanBePrivate") + open class Response( val statusCode: Int, val body: T, val headers: Map> ) { - constructor(statusCode: Int, body: T, headers: Set>>) : this(statusCode, body, toHeadersMap(headers)) + constructor(statusCode: Int, data: T, headers: Set>>) : this( + statusCode, + data, + toHeadersMap(headers) + ) companion object { @JvmStatic @@ -46,7 +50,7 @@ open class Response( ) } - override fun toString(): String = "Response(statusCode=$statusCode, body=$body, headers=$headers)" + override fun toString() = "Response(statusCode=$statusCode, data=$body, headers=$headers)" } class EmptyResponse( diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt similarity index 86% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt index d63c902f..e8c7af85 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/UserAgent.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.model +package com.expediagroup.sdk.core.model -import com.expediagroup.sdk.v2.core.configuration.SdkMetadata +import com.expediagroup.sdk.core.configuration.SdkMetadata /** * Data class representing a user agent which contains information about the Java Development Kit (JDK) version, diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExceptionUtils.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExceptionUtils.kt deleted file mode 100644 index 02fcaabd..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExceptionUtils.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model.exception - -import com.expediagroup.sdk.core.constant.provider.ExceptionMessageProvider -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException - -/** Handles exceptions by ensuring that only instances of [ExpediaGroupException] are thrown. */ -fun Throwable.handle(): Nothing = handleWith(null) - -/** - * Handles exceptions by ensuring that only instances of [ExpediaGroupException] are thrown. - * - * @param transactionId the transaction ID to be included in the exception message, can be null. - */ -fun Throwable.handleWith(transactionId: String?): Nothing { - if (this is ExpediaGroupException) throw this - - when (val cause = this.cause) { - is ExpediaGroupException -> throw cause - else -> throw ExpediaGroupServiceException( - ExceptionMessageProvider.getExceptionOccurredWithTransactionIdMessage(transactionId, message), - this, - transactionId - ) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt index 6d06ffc8..c971e54c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt @@ -21,4 +21,10 @@ package com.expediagroup.sdk.core.model.exception.client * @param invalidFields the names of the invalid fields. */ class ExpediaGroupInvalidFieldNameException(invalidFields: Collection) : - ExpediaGroupClientException("All fields names must contain only alphanumeric characters in addition to - and _ but found [${invalidFields.joinToString(",")}]") + ExpediaGroupClientException( + "All fields names must contain only alphanumeric characters in addition to - and _ but found [${ + invalidFields.joinToString( + "," + ) + }]" + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt index 64e901c5..229fa966 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Expedia, Inc. + * Copyright (C) 2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.expediagroup.sdk.core.model.exception.service -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider.getTransactionIdMessage +import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider +import com.expediagroup.sdk.core.http.HttpStatus +import com.google.api.client.http.HttpResponse -abstract class ExpediaGroupApiException(val statusCode: Int, open val errorObject: Any, transactionId: String?) : - ExpediaGroupServiceException("Unsuccessful response code [$statusCode]${getTransactionIdMessage(transactionId)}${stringifyErrorObject(errorObject.toString())}", transactionId = transactionId) +abstract class ExpediaGroupApiException(val status: HttpStatus, open val errorObject: Any, transactionId: String?) : + ExpediaGroupServiceException("Unsuccessful response code [${status.code}]${ + LoggingMessageProvider.getTransactionIdMessage( + transactionId + ) + }${stringifyErrorObject(errorObject.toString())}", transactionId = transactionId) { + constructor(response: HttpResponse) : this( + HttpStatus.fromCode(response.statusCode), + response.parseAsString(), + response.request.headers.getFirstHeaderStringValue("transaction-id") + ) + } private fun stringifyErrorObject(stringValue: String): String = if (stringValue.isBlank()) " with an empty response body" else ": $stringValue" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt index d3d6ac14..c3cd52ea 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt @@ -15,7 +15,7 @@ */ package com.expediagroup.sdk.core.model.exception.service -import io.ktor.http.HttpStatusCode +import com.expediagroup.sdk.core.http.HttpStatus /** * An exception that is thrown when an authentication error occurs. @@ -32,12 +32,12 @@ class ExpediaGroupAuthException( /** * An exception that is thrown when an authentication error occurs. * - * @param errorCode The HTTP status code of the error. + * @param status The HTTP status of the error. * @param message The error message. */ constructor( - errorCode: HttpStatusCode, + status: HttpStatus, message: String, transactionId: String? - ) : this(message = "[${errorCode.value}] $message", transactionId = transactionId) + ) : this(message = "[${status.code}] $message", transactionId = transactionId) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt deleted file mode 100644 index e25db920..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model.exception.service - -class ExpediaGroupServiceDefaultErrorException(code: Int, override val errorObject: String, transactionId: String?) : ExpediaGroupApiException(code, errorObject, transactionId) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt deleted file mode 100644 index 51666c17..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model.paging - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.HeaderKey.LINK -import com.expediagroup.sdk.core.constant.HeaderKey.PAGINATION_TOTAL_RESULTS -import com.expediagroup.sdk.core.model.Response -import io.ktor.client.statement.HttpResponse - -sealed class BasePaginator( - private val client: Client, - firstResponse: Response, - private val fallbackBody: T, - private val getBody: suspend (HttpResponse) -> T -) : Iterator { - private var state: ResponseState = DefaultResponseState(firstResponse) - val paginationTotalResults: Long = firstResponse.headers[PAGINATION_TOTAL_RESULTS]?.getOrNull(0)?.toLongOrNull() ?: 0 - - override fun hasNext(): Boolean = state.hasNext() - - private fun extractLink(headers: Map>): String? { - return headers[LINK]?.getOrNull(0)?.split(";")?.let { - if (it.isNotEmpty()) it[0] else null - }?.let { - it.substring(it.indexOf("<") + 1, it.indexOf(">")) - } - } - - protected fun nextResponse(): Response { - val response = state.getNextResponse() - state = ResponseStateFactory.getState(extractLink(response.headers), client, fallbackBody, getBody) - return response - } -} - -/** - * Paginator that returns the body of the response. - * - * @param client The client to use to fetch the next response - * @param firstResponse The first response to start the paginator with - * @param getBody A function to extract the body from the response - */ -class Paginator( - client: Client, - firstResponse: Response, - fallbackBody: T, - getBody: suspend (HttpResponse) -> T -) : BasePaginator(client, firstResponse, fallbackBody, getBody) { - /** - * Returns the body of the next response. - * - * @throws NoSuchElementException if the iteration has no next element. - */ - override fun next(): T = nextResponse().body -} - -/** - * Paginator that returns the full response. - * - * @param client The client to use to fetch the next response - * @param firstResponse The first response to start the paginator with - * @param getBody A function to extract the body from the response - */ -class ResponsePaginator( - client: Client, - firstResponse: Response, - fallbackBody: T, - getBody: suspend (HttpResponse) -> T -) : BasePaginator, T>(client, firstResponse, fallbackBody, getBody) { - /** - * Returns the next response. - * - * @throws NoSuchElementException if the iteration has no next element. - */ - override fun next(): Response = nextResponse() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt deleted file mode 100644 index 1742ecbb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model.paging - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.v2.core.constant.HeaderValue -import com.expediagroup.sdk.core.model.Response -import com.expediagroup.sdk.core.plugin.logging.GZipEncoder.decode -import com.expediagroup.sdk.core.plugin.logging.contentEncoding -import io.ktor.client.statement.HttpResponse -import io.ktor.util.InternalAPI -import io.ktor.util.moveToByteArray -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.bits.Memory -import kotlinx.coroutines.runBlocking -import java.nio.ByteBuffer - -internal interface ResponseState { - fun getNextResponse(): Response - - fun hasNext(): Boolean -} - -internal class DefaultResponseState( - private val response: Response -) : ResponseState { - override fun getNextResponse(): Response { - return response - } - - override fun hasNext(): Boolean { - return true - } -} - -internal class LastResponseState : ResponseState { - override fun getNextResponse(): Response { - throw NoSuchElementException() - } - - override fun hasNext(): Boolean { - return false - } -} - -internal class FetchLinkState( - private val link: String, - private val client: Client, - private val fallbackBody: T, - private val getBody: suspend (HttpResponse) -> T -) : ResponseState { - override fun getNextResponse(): Response { - return runBlocking { - val response = client.performGet(link) - val body = parseBody(response) - Response(response.status.value, body, response.headers.entries()) - } - } - - override fun hasNext(): Boolean { - return true - } - - private suspend fun parseBody(response: HttpResponse): T { - return if (decodeBody(response).isEmpty()) fallbackBody else getBody(response) - } - - private suspend fun decodeBody(response: HttpResponse): String { - val byteReadChannel = prepareByteReadChannel(response) - val decodedByteReadChannel = if (response.contentEncoding().equals(HeaderValue.GZIP)) client.httpClient.decode(byteReadChannel) else byteReadChannel - val bodyString: String = decodedByteReadChannel.readRemaining().readText() - return bodyString - } - - @OptIn(InternalAPI::class) - private suspend fun prepareByteReadChannel(response: HttpResponse): ByteReadChannel { - val bufferSize = response.content.availableForRead - val buffer = ByteBuffer.allocate(bufferSize) - val numberOfBytesRead = response.content.peekTo(Memory(buffer), 0, 0, 0, bufferSize.toLong()).toInt() - val byteReadChannel = ByteReadChannel(buffer.moveToByteArray(), 0, numberOfBytesRead) - return byteReadChannel - } -} - -internal class ResponseStateFactory { - companion object { - fun getState( - link: String?, - client: Client, - fallbackBody: T, - getBody: suspend (HttpResponse) -> T - ): ResponseState { - return link?.let { FetchLinkState(it, client, fallbackBody, getBody) } ?: LastResponseState() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Hook.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Hook.kt deleted file mode 100644 index 9a688c6b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Hook.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin - -import com.expediagroup.sdk.core.client.Client - -/** - * A helper to build a hook. - */ -internal interface HookBuilder { - fun build(configs: C) -} - -/** - * A hook is a post action we need to apply after creating a [Client]. - */ -internal open class Hook( - private val configuration: C, - private val builder: HookBuilder -) { - fun execute() = builder.build(configuration) -} - -/** A singleton repository of all [Hook]s we need to apply on the [Client]. */ -internal object Hooks { - private val clientsHooks: MutableMap>> = mutableMapOf() - - fun add( - client: Client, - hook: Hook - ) { - clientsHooks.getOrPut(client) { mutableListOf() } += hook - } - - fun execute(client: Client) { - clientsHooks[client]?.forEach { it.execute() } - } -} - -/** - * Provide an idiomatic scope to define hooks. - */ -internal fun Client.hooks(block: HookContext.() -> Unit) = block(HookContext(this)) - -internal class HookContext(private val client: Client) { - /** - * Provides an idiomatic way of defining a hook. - */ - fun use(hookFactory: HookFactory) = hookFactory - - /** - * Provides an idiomatic way of configuring a hook. - */ - fun HookFactory.with(config: C) = Hooks.add(client, create(client, config)) -} - -internal interface HookFactory { - fun create( - client: Client, - configuration: C - ): Hook -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Plugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Plugin.kt deleted file mode 100644 index 6a81db9d..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Plugin.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin - -import com.expediagroup.sdk.core.client.Client - -internal interface Plugin { - /** Install a plugin. */ - fun install( - client: Client, - configurations: C - ) -} - -/** - * Provide an idiomatic scope to define plugins. - */ -internal fun Client.plugins(block: PluginContext.() -> Unit) = block(PluginContext(this)) - -internal class PluginContext(private val client: Client) { - /** - * Provides an idiomatic way of starting a plugin. - */ - fun > use(plugin: P): P = plugin - - /** - * Provides an idiomatic way of configuring a plugin. - */ - fun Plugin.with(configs: C) = install(client, configs) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/PluginConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/PluginConfiguration.kt deleted file mode 100644 index 8eafdd38..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/PluginConfiguration.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin - -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal interface PluginConfiguration - -internal abstract class KtorPluginConfiguration(open val httpClientConfiguration: HttpClientConfig) : PluginConfiguration diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationConfiguration.kt deleted file mode 100644 index 4e0a69c2..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationConfiguration.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication - -import com.expediagroup.sdk.core.configuration.Credentials -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal data class AuthenticationConfiguration( - override val httpClientConfiguration: HttpClientConfig, - val credentials: Credentials, - val authUrl: String, - val authType: AuthenticationType -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from( - httpClientConfig: HttpClientConfig, - credentials: Credentials, - authUrl: String, - authType: AuthenticationType = AuthenticationType.BEARER - ) = AuthenticationConfiguration(httpClientConfig, credentials, authUrl, authType) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationHookFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationHookFactory.kt deleted file mode 100644 index e919f21a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationHookFactory.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.Authentication.AUTHORIZATION_REQUEST_LOCK_DELAY -import com.expediagroup.sdk.core.plugin.Hook -import com.expediagroup.sdk.core.plugin.HookBuilder -import com.expediagroup.sdk.core.plugin.HookFactory -import io.ktor.client.plugins.HttpSend -import io.ktor.client.plugins.plugin -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.http.HttpHeaders -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.delay - -internal object AuthenticationHookFactory : HookFactory { - override fun create( - client: Client, - configuration: AuthenticationConfiguration - ): Hook { - return Hook(configuration, AuthenticationHookBuilder(client)) - } -} - -private class AuthenticationHookBuilder(private val client: Client) : HookBuilder { - private val lock = atomic(false) - private val authenticationStrategy = client.getAuthenticationStrategy() - - override fun build(configs: AuthenticationConfiguration) { - val httpClient = client.httpClient - - httpClient.plugin(HttpSend).intercept { request -> - if (!authenticationStrategy.isIdentityRequest(request)) { - if (authenticationStrategy.isTokenAboutToExpire()) { - if (!lock.getAndSet(true)) { - try { - authenticationStrategy.renewToken() - } finally { - lock.compareAndSet(expect = true, update = false) - } - } - } - while (lock.value) delay(AUTHORIZATION_REQUEST_LOCK_DELAY) - assignLatestToken(request) - } - execute(request) - } - } - - private fun assignLatestToken(request: HttpRequestBuilder) { - request.headers[HttpHeaders.Authorization] = authenticationStrategy.getAuthorizationHeader() - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationPlugin.kt deleted file mode 100644 index 174f9fee..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationPlugin.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.ExceptionMessage.AUTHENTICATION_NOT_CONFIGURED_FOR_CLIENT -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupClientException -import com.expediagroup.sdk.core.plugin.Plugin -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy -import io.ktor.client.plugins.auth.Auth -import kotlin.collections.set - -internal object AuthenticationPlugin : Plugin { - val clientAuthenticationStrategies = mutableMapOf() - - override fun install( - client: Client, - configurations: AuthenticationConfiguration - ) { - val strategy = AuthenticationStrategy.from(configurations, client) - clientAuthenticationStrategies[client] = strategy - configurations.httpClientConfiguration.install(Auth) { - strategy.loadAuth(this) - } - } -} - -internal fun Client.getAuthenticationStrategy(): AuthenticationStrategy = - AuthenticationPlugin.clientAuthenticationStrategies[this] ?: throw ExpediaGroupClientException(AUTHENTICATION_NOT_CONFIGURED_FOR_CLIENT) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/AuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/AuthenticationStrategy.kt deleted file mode 100644 index 34ee3000..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/AuthenticationStrategy.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication.strategy - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType.BEARER -import com.expediagroup.sdk.core.plugin.authentication.strategy.AuthenticationStrategy.AuthenticationType.SIGNATURE -import io.ktor.client.plugins.auth.Auth -import io.ktor.client.request.HttpRequestBuilder - -internal interface AuthenticationStrategy { - fun loadAuth(auth: Auth) {} - - fun isTokenAboutToExpire(): Boolean - - fun renewToken() - - fun isIdentityRequest(request: HttpRequestBuilder): Boolean - - fun getAuthorizationHeader(): String - - companion object { - fun from( - configs: AuthenticationConfiguration, - client: Client - ): AuthenticationStrategy = - when (configs.authType) { - BEARER -> ExpediaGroupAuthenticationStrategy(client, configs) - SIGNATURE -> RapidAuthenticationStrategy(configs) - } - } - - enum class AuthenticationType { - BEARER, - SIGNATURE - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/ExpediaGroupAuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/ExpediaGroupAuthenticationStrategy.kt deleted file mode 100644 index fdd80af0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/ExpediaGroupAuthenticationStrategy.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication.strategy - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.configuration.Credentials -import com.expediagroup.sdk.core.constant.Authentication -import com.expediagroup.sdk.core.constant.Constant -import com.expediagroup.sdk.core.constant.ExceptionMessage -import com.expediagroup.sdk.core.constant.LoggingMessage -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.expediagroup.sdk.core.model.getTransactionId -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration -import com.expediagroup.sdk.core.plugin.logging.ExpediaGroupLoggerFactory -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.ObjectMapper -import io.ktor.client.HttpClient -import io.ktor.client.plugins.auth.Auth -import io.ktor.client.plugins.auth.providers.BearerAuthProvider -import io.ktor.client.plugins.auth.providers.BearerTokens -import io.ktor.client.plugins.auth.providers.bearer -import io.ktor.client.plugins.plugin -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.client.request.basicAuth -import io.ktor.client.request.parameter -import io.ktor.client.request.request -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.ParametersBuilder -import io.ktor.http.clone -import io.ktor.http.contentType -import kotlinx.coroutines.runBlocking -import java.time.LocalDateTime - -internal class ExpediaGroupAuthenticationStrategy( - private val client: Client, - private val configs: AuthenticationConfiguration, -) : AuthenticationStrategy { - private val log = ExpediaGroupLoggerFactory.getLogger(javaClass) - private var bearerTokenStorage = BearerTokensInfo.emptyBearerTokenInfo - private val objectMapper = ObjectMapper() - - override fun loadAuth(auth: Auth) { - auth.bearer { - sendWithoutRequest { request -> - isIdentityRequest(request) - } - } - } - - override fun isTokenAboutToExpire(): Boolean = - bearerTokenStorage.isAboutToExpire().also { if (it) log.info(LoggingMessage.TOKEN_EXPIRED) } - - override fun renewToken() { - val httpClient = client.httpClient - log.info(LoggingMessage.TOKEN_RENEWAL_IN_PROGRESS) - clearTokens(httpClient) - val renewTokenResponse = - runBlocking { - httpClient.request { - method = HttpMethod.Post - parameter(Authentication.GRANT_TYPE, Authentication.CLIENT_CREDENTIALS) - contentType(ContentType.Application.FormUrlEncoded) - url(configs.authUrl) - basicAuth(configs.credentials) - with(client) { appendHeaders() } - } - } - if (renewTokenResponse.status.value !in Constant.SUCCESSFUL_STATUS_CODES_RANGE) { - throw ExpediaGroupAuthException( - renewTokenResponse.status, - ExceptionMessage.AUTHENTICATION_FAILURE, - renewTokenResponse.headers.getTransactionId(), - ) - } - val renewedTokenInfo: TokenResponse = - runBlocking { - objectMapper.readValue(renewTokenResponse.bodyAsText(), TokenResponse::class.java) - } - log.info(LoggingMessage.TOKEN_RENEWAL_SUCCESSFUL) - log.info(LoggingMessageProvider.getTokenExpiresInMessage(renewedTokenInfo.expiresIn)) - bearerTokenStorage = - BearerTokensInfo( - BearerTokens(renewedTokenInfo.accessToken, renewedTokenInfo.accessToken), - renewedTokenInfo.expiresIn, - ) - bearerTokenStorage - } - - private fun clearTokens(client: HttpClient) { - log.info(LoggingMessage.TOKEN_CLEARING_IN_PROGRESS) - client.plugin(Auth).providers.filterIsInstance().first().clearToken() - bearerTokenStorage = BearerTokensInfo.emptyBearerTokenInfo - log.info(LoggingMessage.TOKEN_CLEARING_SUCCESSFUL) - } - - private fun getTokens(): BearerTokens = bearerTokenStorage.bearerTokens - - private fun HttpRequestBuilder.basicAuth(credentials: Credentials) { - basicAuth( - credentials.key, - credentials.secret, - ) - } - - override fun isIdentityRequest(request: HttpRequestBuilder): Boolean = - request.url.clone().apply { - encodedParameters = ParametersBuilder() - }.buildString() == configs.authUrl - - override fun getAuthorizationHeader() = "${Authentication.BEARER} ${getTokens().accessToken}" - - internal open class BearerTokensInfo(val bearerTokens: BearerTokens, expiresIn: Int) { - private val expiryDate: LocalDateTime - - init { - this.expiryDate = calculateExpiryDate(expiresIn) - } - - private fun calculateExpiryDate(expiresIn: Int): LocalDateTime = LocalDateTime.now().plusSeconds(expiresIn.toLong()) - - open fun isAboutToExpire(): Boolean = LocalDateTime.now().isAfter(expiryDate.minusSeconds(Authentication.BEARER_EXPIRY_DATE_MARGIN)) - - companion object { - internal val emptyBearerTokenInfo = - object : BearerTokensInfo(BearerTokens(Constant.EMPTY_STRING, Constant.EMPTY_STRING), -1) { - override fun isAboutToExpire() = true - } - } - } - - internal data class TokenResponse - @JsonCreator - constructor( - @JsonProperty("access_token") val accessToken: String, - @JsonProperty("expires_in") val expiresIn: Int, - @JsonProperty("token_type") val tokenType: String, - @JsonProperty("scope") val scope: String, - ) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/RapidAuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/RapidAuthenticationStrategy.kt deleted file mode 100644 index 0927b756..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/RapidAuthenticationStrategy.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.authentication.strategy - -import com.expediagroup.sdk.core.constant.Authentication -import com.expediagroup.sdk.core.constant.Constant -import com.expediagroup.sdk.core.constant.SignatureValues -import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration -import io.ktor.client.request.HttpRequestBuilder -import java.math.BigInteger -import java.nio.charset.StandardCharsets -import java.security.MessageDigest -import java.security.spec.MGF1ParameterSpec -import java.time.Instant - -internal class RapidAuthenticationStrategy(private val configs: AuthenticationConfiguration) : AuthenticationStrategy { - private var signature: String = Constant.EMPTY_STRING - - override fun isTokenAboutToExpire(): Boolean = true - - override fun renewToken() { - val credentials = configs.credentials - signature = calculateSignature(credentials.key, credentials.secret, Instant.now().epochSecond) - } - - override fun isIdentityRequest(request: HttpRequestBuilder) = false - - override fun getAuthorizationHeader() = createAuthorizationHeader(signature) - - private fun createAuthorizationHeader(signature: String?): String = "${Authentication.EAN} $signature" - - private fun calculateSignature( - apiKey: String, - secret: String, - timestamp: Long - ): String { - val toBeHashed = apiKey + secret + timestamp - val messageDigest = MessageDigest.getInstance(MGF1ParameterSpec.SHA512.digestAlgorithm) - val bytes = messageDigest.digest(toBeHashed.toByteArray(StandardCharsets.UTF_8)) - val signature = - buildString { - bytes.forEach { - append(((it.toInt() and SignatureValues.ONE_BYTE_MASK) + SignatureValues.INCREMENT).toString(SignatureValues.RADIX).substring(BigInteger.ONE.toInt())) - } - } - return "${SignatureValues.API_KEY}=$apiKey,${SignatureValues.SIGNATURE}=$signature,${SignatureValues.TIMESTAMP}=$timestamp" - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingConfiguration.kt deleted file mode 100644 index c789bc69..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingConfiguration.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.encoding - -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal data class EncodingConfiguration( - override val httpClientConfiguration: HttpClientConfig -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from(httpClientConfig: HttpClientConfig) = EncodingConfiguration(httpClientConfig) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingPlugin.kt deleted file mode 100644 index 658dd636..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingPlugin.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.encoding - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.Plugin -import io.ktor.client.plugins.compression.ContentEncoding - -internal object EncodingPlugin : Plugin { - override fun install( - client: Client, - configurations: EncodingConfiguration - ) { - configurations.httpClientConfiguration.install(ContentEncoding) { - gzip() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingConfiguration.kt deleted file mode 100644 index 89cdd14c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingConfiguration.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.exception - -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal data class ExceptionHandlingConfiguration( - override val httpClientConfiguration: HttpClientConfig -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from(httpClientConfig: HttpClientConfig) = ExceptionHandlingConfiguration(httpClientConfig) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingPlugin.kt deleted file mode 100644 index 15d58a13..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingPlugin.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.exception - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.model.exception.handleWith -import com.expediagroup.sdk.core.model.getTransactionId -import com.expediagroup.sdk.core.plugin.Plugin -import io.ktor.client.plugins.HttpResponseValidator - -internal object ExceptionHandlingPlugin : Plugin { - override fun install( - client: Client, - configurations: ExceptionHandlingConfiguration - ) { - with(configurations.httpClientConfiguration) { - HttpResponseValidator { - handleResponseExceptionWithRequest { exception, request -> - exception.handleWith(request.headers.getTransactionId()) - } - } - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutConfiguration.kt deleted file mode 100644 index a0d28f3c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutConfiguration.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.httptimeout - -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal data class HttpTimeoutConfiguration( - override val httpClientConfiguration: HttpClientConfig, - val requestTimeout: Long, - val connectionTimeout: Long, - val socketTimeout: Long -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from( - httpClientConfig: HttpClientConfig, - requestTimeout: Long, - connectionTimeout: Long, - socketTimeout: Long - ) = HttpTimeoutConfiguration(httpClientConfig, requestTimeout, connectionTimeout, socketTimeout) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutPlugin.kt deleted file mode 100644 index 0590d884..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutPlugin.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.httptimeout - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.Plugin -import io.ktor.client.plugins.HttpTimeout - -internal object HttpTimeoutPlugin : Plugin { - override fun install( - client: Client, - configurations: HttpTimeoutConfiguration - ) { - configurations.httpClientConfiguration.install(HttpTimeout) { - requestTimeoutMillis = configurations.requestTimeout - connectTimeoutMillis = configurations.connectionTimeout - socketTimeoutMillis = configurations.socketTimeout - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.kt deleted file mode 100644 index 78c55920..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.LogMaskingFields -import com.expediagroup.sdk.core.constant.LoggingMessage.LOGGING_PREFIX -import org.slf4j.Logger - -internal class ExpediaGroupLogger(private val logger: Logger, private val client: Client? = null) : Logger by logger { - override fun info(msg: String) = logger.info(decorate(msg)) - - override fun warn(msg: String) = logger.warn(decorate(msg)) - - override fun debug(msg: String) = logger.debug(decorate(msg)) - - private fun decorate(msg: String): String = "$LOGGING_PREFIX ${mask(msg, getMaskedBodyFields())}" - - private fun getMaskedBodyFields(): Set = client?.getLoggingMaskedFieldsProvider()?.getMaskedBodyFields() ?: LogMaskingFields.DEFAULT_MASKED_BODY_FIELDS -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLoggerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLoggerFactory.kt deleted file mode 100644 index e2d2326e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLoggerFactory.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.client.Client -import org.slf4j.LoggerFactory - -internal object ExpediaGroupLoggerFactory { - fun getLogger(clazz: Class<*>) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz)) - - fun getLogger( - clazz: Class<*>, - client: Client - ) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz), client) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LogMasker.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LogMasker.kt deleted file mode 100644 index b22a6d64..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LogMasker.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.constant.LogMaskingRegex.NUMBER_FIELD_REGEX -import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED -import com.expediagroup.sdk.core.constant.provider.LogMaskingRegexProvider.getMaskedFieldsRegex - -internal fun mask( - message: String, - maskedBodyFields: Set -): String = masks.fold(message) { acc, mask -> mask.mask(acc, maskedBodyFields) } - -internal fun interface Mask { - fun getRegex(maskedBodyFields: Set): Regex - - fun mask( - string: String, - maskedBodyFields: Set - ): String = string.replace(this.getRegex(maskedBodyFields)) { maskSubstring(it.value) } - - fun maskSubstring(string: String) = OMITTED -} - -internal val masks: List = - listOf( - Mask(::getMaskedFieldsRegex), - Mask { NUMBER_FIELD_REGEX } - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingConfiguration.kt deleted file mode 100644 index 70787421..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingConfiguration.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logger - -internal data class LoggingConfiguration( - override val httpClientConfiguration: HttpClientConfig, - val maskedLoggingHeaders: Set, - val maskedLoggingBodyFields: Set, - val level: LogLevel = LogLevel.ALL, - val getLogger: (client: Client) -> Logger = createCustomLogger -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from( - httpClientConfig: HttpClientConfig, - maskedLoggingHeaders: Set, - maskedLoggingBodyFields: Set - ) = LoggingConfiguration(httpClientConfig, maskedLoggingHeaders, maskedLoggingBodyFields) - } -} - -private val createCustomLogger: (client: Client) -> Logger - get() = { - object : Logger { - val delegate = ExpediaGroupLoggerFactory.getLogger(Client::class.java, it) - - override fun log(message: String) = delegate.info(message) - } - } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingMaskedFieldsProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingMaskedFieldsProvider.kt deleted file mode 100644 index 033ff1f6..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingMaskedFieldsProvider.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.constant.LogMaskingFields.DEFAULT_MASKED_BODY_FIELDS -import com.expediagroup.sdk.core.constant.LogMaskingFields.DEFAULT_MASKED_HEADER_FIELDS -import com.expediagroup.sdk.core.constant.LogMaskingRegex.FIELD_REGEX -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupInvalidFieldNameException - -class LoggingMaskedFieldsProvider(maskedLoggingHeaders: Set, maskedLoggingBodyFields: Set) { - private val maskedHeaderFields: Set - private val maskedBodyFields: Set - - init { - maskedLoggingHeaders.filter(::isInvalid).takeIf { it.isNotEmpty() }?.let { throw ExpediaGroupInvalidFieldNameException(it) } - maskedLoggingBodyFields.filter(::isInvalid).takeIf { it.isNotEmpty() }?.let { throw ExpediaGroupInvalidFieldNameException(it) } - maskedHeaderFields = DEFAULT_MASKED_HEADER_FIELDS.union(maskedLoggingHeaders) - maskedBodyFields = DEFAULT_MASKED_BODY_FIELDS.union(maskedLoggingBodyFields) - } - - /** - * @return a copy of the list of headers to be masked - */ - fun getMaskedHeaderFields(): Set = maskedHeaderFields.toSet() - - /** - * @return a copy of the list of body fields to be masked - */ - fun getMaskedBodyFields(): Set = maskedBodyFields.toSet() - - private fun isInvalid(fieldName: String): Boolean = !fieldName.matches(FIELD_REGEX) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingPlugin.kt deleted file mode 100644 index 758a5df7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingPlugin.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.constant.ExceptionMessage -import com.expediagroup.sdk.core.constant.LoggingMessage -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupClientException -import com.expediagroup.sdk.core.plugin.Plugin -import io.ktor.client.plugins.logging.Logging - -internal object LoggingPlugin : Plugin { - val clientLoggingMaskedFieldsProviders = mutableMapOf() - - override fun install( - client: Client, - configurations: LoggingConfiguration - ) { - clientLoggingMaskedFieldsProviders[client] = - LoggingMaskedFieldsProvider( - configurations.maskedLoggingHeaders, - configurations.maskedLoggingBodyFields - ) - configurations.httpClientConfiguration.install(Logging) { - logger = configurations.getLogger(client) - level = configurations.level - sanitizeHeader(LoggingMessage.OMITTED) { header -> - client.getLoggingMaskedFieldsProvider().getMaskedHeaderFields().contains(header) - } - } - configurations.httpClientConfiguration.install(RequestBodyLogger) - configurations.httpClientConfiguration.install(ResponseBodyLogger) - } -} - -internal fun Client.getLoggingMaskedFieldsProvider(): LoggingMaskedFieldsProvider = - LoggingPlugin.clientLoggingMaskedFieldsProviders[this] ?: throw ExpediaGroupClientException(ExceptionMessage.LOGGING_MASKED_FIELDS_NOT_CONFIGURED_FOR_CLIENT) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt deleted file mode 100644 index 867819d0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.v2.core.constant.LoggerName -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.model.getTransactionId -import io.ktor.client.HttpClient -import io.ktor.client.plugins.HttpClientPlugin -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.client.request.HttpSendPipeline -import io.ktor.http.content.OutputStreamContent -import io.ktor.util.AttributeKey -import io.ktor.util.pipeline.PipelineContext -import io.ktor.utils.io.ByteChannel - -internal class RequestBodyLogger { - private val log = ExpediaGroupLoggerFactory.getLogger(javaClass) - - companion object Plugin : HttpClientPlugin { - override val key: AttributeKey = AttributeKey(LoggerName.REQUEST_BODY_LOGGER) - - override fun install( - plugin: RequestBodyLogger, - scope: HttpClient - ) { - scope.sendPipeline.intercept(HttpSendPipeline.Monitoring) { - val body: String = getBody() - plugin.log.debug(LoggingMessageProvider.getRequestBodyMessage(body, context.headers.getTransactionId())) - proceed() - } - } - - private suspend fun PipelineContext.getBody(): String { - if (context.headers.get("Content-Type")?.contains("multipart") == true) { - return "<-- Multipart Request -->" - } - - val body = context.body - if (body is OutputStreamContent) { - with(ByteChannel()) { - body.writeTo(this) - return readRemaining().readText() - } - } - return body.toString() - } - - override fun prepare(block: RequestBodyLoggerConfig.() -> Unit): RequestBodyLogger { - return RequestBodyLogger() - } - } -} - -internal class RequestBodyLoggerConfig diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt deleted file mode 100644 index 06aa66ba..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.logging - -import com.expediagroup.sdk.v2.core.constant.HeaderValue -import com.expediagroup.sdk.v2.core.constant.LoggerName -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.model.getTransactionId -import com.expediagroup.sdk.core.plugin.logging.GZipEncoder.decode -import io.ktor.client.HttpClient -import io.ktor.client.plugins.HttpClientPlugin -import io.ktor.client.plugins.compression.ContentEncoder -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.HttpResponsePipeline -import io.ktor.client.statement.request -import io.ktor.http.HttpHeaders -import io.ktor.util.AttributeKey -import io.ktor.util.Encoder -import io.ktor.util.GZip -import io.ktor.util.InternalAPI -import io.ktor.utils.io.ByteReadChannel - -class ResponseBodyLogger { - private val log = ExpediaGroupLoggerFactory.getLogger(javaClass) - - companion object Plugin : HttpClientPlugin { - override val key: AttributeKey = AttributeKey(LoggerName.RESPONSE_BODY_LOGGER) - - @OptIn(InternalAPI::class) - override fun install( - plugin: ResponseBodyLogger, - scope: HttpClient - ) { - scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { - val response: HttpResponse = context.response - - if (response.headers.get(HttpHeaders.ContentType)?.contains("application/json") == false) { - plugin.log.debug(LoggingMessageProvider.getResponseBodyMessage("<-- Multipart Response -->", response.request.headers.getTransactionId())) - proceed() - return@intercept - } - - val byteReadChannel: ByteReadChannel = if (response.contentEncoding().equals(HeaderValue.GZIP)) scope.decode(response.content) else response.content - val body: String = byteReadChannel.readRemaining().readText() - plugin.log.debug(LoggingMessageProvider.getResponseBodyMessage(body, response.request.headers.getTransactionId())) - proceed() - } - } - - override fun prepare(block: ResponseBodyLoggerConfig.() -> Unit): ResponseBodyLogger { - return ResponseBodyLogger() - } - } -} - -fun HttpResponse.contentEncoding(): String? = headers[HttpHeaders.ContentEncoding] - -internal object GZipEncoder : ContentEncoder, Encoder by GZip { - override val name: String = HeaderValue.GZIP -} - -class ResponseBodyLoggerConfig diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestConfiguration.kt deleted file mode 100644 index c1114a22..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestConfiguration.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.request - -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig - -internal data class DefaultRequestConfiguration( - override val httpClientConfiguration: HttpClientConfig, - val endpoint: String -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from( - httpClientConfig: HttpClientConfig, - endpoint: String - ) = DefaultRequestConfiguration(httpClientConfig, endpoint) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestPlugin.kt deleted file mode 100644 index 745c6771..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestPlugin.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.request - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.Plugin -import io.ktor.client.plugins.DefaultRequest - -internal object DefaultRequestPlugin : Plugin { - override fun install( - client: Client, - configurations: DefaultRequestConfiguration - ) { - configurations.httpClientConfiguration.install(DefaultRequest) { - url(configurations.endpoint) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationConfiguration.kt deleted file mode 100644 index dc1546df..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationConfiguration.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.serialization - -import com.expediagroup.sdk.core.plugin.KtorPluginConfiguration -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.HttpClientEngineConfig -import io.ktor.http.ContentType - -internal data class SerializationConfiguration( - override val httpClientConfiguration: HttpClientConfig, - val contentType: ContentType = ContentType.Application.Json -) : KtorPluginConfiguration(httpClientConfiguration) { - companion object { - fun from(httpClientConfig: HttpClientConfig) = SerializationConfiguration(httpClientConfig) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationPlugin.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationPlugin.kt deleted file mode 100644 index 08153243..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationPlugin.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.plugin.serialization - -import com.expediagroup.sdk.core.client.Client -import com.expediagroup.sdk.core.plugin.Plugin -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.PropertyNamingStrategies -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.serialization.jackson.jackson -import java.text.SimpleDateFormat - -internal object SerializationPlugin : Plugin { - override fun install( - client: Client, - configurations: SerializationConfiguration, - ) { - configurations.httpClientConfiguration.install(ContentNegotiation) { - jackson { - enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) - disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) - setDateFormat(SimpleDateFormat()) - findAndRegisterModules() - } - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt index 388f17d2..d50b7f43 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/Request.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.request +package com.expediagroup.sdk.core.request -import com.expediagroup.sdk.v2.core.jackson.deserialize -import com.expediagroup.sdk.v2.core.model.Operation +import com.expediagroup.sdk.core.jackson.deserialize +import com.expediagroup.sdk.core.model.Operation import com.fasterxml.jackson.core.type.TypeReference import com.google.api.client.googleapis.services.AbstractGoogleClient import com.google.api.client.googleapis.services.AbstractGoogleClientRequest diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt index de83f9fc..86bfaa32 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/ApiClientRequestInitializer.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.request.initializer +package com.expediagroup.sdk.core.request.initializer -import com.expediagroup.sdk.v2.core.model.UserAgent +import com.expediagroup.sdk.core.model.UserAgent import com.google.api.client.googleapis.services.AbstractGoogleClientRequest import com.google.api.client.googleapis.services.CommonGoogleClientRequestInitializer diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt similarity index 70% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt index 39a33fb5..4e3fa510 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/InitializerFunctions.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.request.initializer +package com.expediagroup.sdk.core.request.initializer -import com.expediagroup.sdk.v2.core.request.interceptor.HttpRequestLoggingInterceptor -import com.expediagroup.sdk.v2.core.request.interceptor.HttpResponseLoggingInterceptor +import com.expediagroup.sdk.core.request.interceptor.HttpRequestLoggingInterceptor +import com.expediagroup.sdk.core.request.interceptor.HttpResponseLoggingInterceptor import com.google.api.client.http.HttpRequest object InitializerFunctions { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt index d66c2e43..b3780bcf 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/initializer/SdkRequestInitializer.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.request.initializer +package com.expediagroup.sdk.core.request.initializer import com.google.api.client.http.HttpRequest import com.google.api.client.http.HttpRequestInitializer diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt index 0c94ca4e..c601eea7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpRequestLoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt @@ -1,12 +1,12 @@ -package com.expediagroup.sdk.v2.core.request.interceptor - -import com.expediagroup.sdk.v2.core.constant.LoggingMessage.OMITTED -import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLogger -import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.v2.core.logging.LogMessageConstant -import com.expediagroup.sdk.v2.core.logging.LogMessageTag -import com.expediagroup.sdk.v2.core.logging.LOGGABLE_CONTENT_TYPES -import com.expediagroup.sdk.v2.core.logging.mask.isMaskedField +package com.expediagroup.sdk.core.request.interceptor + +import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED +import com.expediagroup.sdk.core.logging.ExpediaGroupLogger +import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.core.logging.LogMessageConstant +import com.expediagroup.sdk.core.logging.LogMessageTag +import com.expediagroup.sdk.core.logging.LOGGABLE_CONTENT_TYPES +import com.expediagroup.sdk.core.logging.mask.isMaskedField import com.google.api.client.http.HttpExecuteInterceptor import com.google.api.client.http.HttpRequest import com.google.api.client.http.InputStreamContent diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt index 5a55409d..15ffcbe7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/request/interceptor/HttpResponseLoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt @@ -1,12 +1,12 @@ -package com.expediagroup.sdk.v2.core.request.interceptor - -import com.expediagroup.sdk.v2.core.constant.LoggingMessage.OMITTED -import com.expediagroup.sdk.v2.core.logging.LogMessageConstant -import com.expediagroup.sdk.v2.core.logging.LOGGABLE_CONTENT_TYPES -import com.expediagroup.sdk.v2.core.logging.LogMessageTag -import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLogger -import com.expediagroup.sdk.v2.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.v2.core.logging.mask.isMaskedField +package com.expediagroup.sdk.core.request.interceptor + +import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED +import com.expediagroup.sdk.core.logging.LogMessageConstant +import com.expediagroup.sdk.core.logging.LOGGABLE_CONTENT_TYPES +import com.expediagroup.sdk.core.logging.LogMessageTag +import com.expediagroup.sdk.core.logging.ExpediaGroupLogger +import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory +import com.expediagroup.sdk.core.logging.mask.isMaskedField import com.google.api.client.http.HttpResponse import com.google.api.client.http.HttpResponseInterceptor import okio.IOException diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt similarity index 82% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt index 071670e2..389087fc 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/Trait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait +package com.expediagroup.sdk.core.trait /** * Marker interface for defining traits in a class. Traits serve as a means to define common diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt similarity index 72% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt index c1d8a51b..1f38c571 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/AuthenticationHandlerTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.trait.authentication +package com.expediagroup.sdk.core.trait.authentication -import com.expediagroup.sdk.v2.core.trait.Trait -import com.expediagroup.sdk.v2.core.trait.configuration.ClientConfiguration +import com.expediagroup.sdk.core.trait.Trait +import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration import com.google.api.client.http.HttpTransport /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt similarity index 78% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt index 4c480dc5..722da5a5 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/authentication/RefreshAccessTokenTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.authentication +package com.expediagroup.sdk.core.trait.authentication -import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.core.trait.Trait import com.google.auth.oauth2.AccessToken /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt similarity index 68% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt index aeccafd5..8cd6035b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/BuilderTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.common +package com.expediagroup.sdk.core.trait.common -import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.core.trait.Trait /** * Defines a trait for building instances of a specific type. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt similarity index 81% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt index 71b1886c..6fe1e5cd 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/ConfigurationTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.common +package com.expediagroup.sdk.core.trait.common -import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.core.trait.Trait /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt similarity index 80% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt index 0f2ec87c..4f6f462b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/common/IdTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.common +package com.expediagroup.sdk.core.trait.common -import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.core.trait.Trait import java.util.UUID /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt similarity index 87% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt index 20912259..cfb29f41 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthEndpointTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface representing a trait that provides the authentication endpoint. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt similarity index 80% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt index 4a868a30..de152b5f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/AuthenticationStrategyTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration -import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy /** * Interface representing a trait for authentication strategies in client configurations. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt similarity index 74% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt index ed6e52bd..e51b972a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration -import com.expediagroup.sdk.v2.core.trait.common.ConfigurationTrait -import com.expediagroup.sdk.v2.core.trait.common.IdTrait +import com.expediagroup.sdk.core.trait.common.ConfigurationTrait +import com.expediagroup.sdk.core.trait.common.IdTrait /** * Interface that combines configuration traits with an identifier. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt similarity index 67% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt index 969547b2..36b275fe 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ClientConfigurationTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration -import com.expediagroup.sdk.v2.core.trait.common.ConfigurationTrait +import com.expediagroup.sdk.core.trait.common.ConfigurationTrait /** * Interface representing client-specific configuration traits. This interface inherits diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt index 6953d8d4..a20698f7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ConnectionTimeoutTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface for configuration traits that require specifying the connection timeout duration. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt similarity index 82% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt index f26a5086..a4fbc75e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/EndpointTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface that represents an endpoint trait in a client configuration. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt index 7bfe96ea..d9d751b0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/FullConfigurationTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface representing a full configuration trait which amalgamates diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt index 09ec2c93..90adc8b3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/KeyTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * KeyTrait interface for client configurations that provides access to the client's key. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt index 119ee5c6..6c31de89 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Trait for managing the body fields that should be masked in logs. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt similarity index 89% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt index 5c5a49e1..97c375ee 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaskedLoggingHeadersTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Trait for managing the headers that should be masked in logs. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt similarity index 91% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt index 88b51bcb..530a8f1d 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsPerRouteTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface for configuring the maximum number of connections allowed per route. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt index 5f34b911..b8faa760 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/MaxConnectionsTotalTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface defining a trait for configuring the maximum number of total connections. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt index cf4d55b4..05a53dd1 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/RequestTimeoutTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface for managing the request timeout configuration. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt index 3c9372cb..8e406dd1 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SecretTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Interface representing a configuration trait that provides access to a secret. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt similarity index 90% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt index 751add8b..236fa6e3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/SocketTimeoutTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration /** * Trait representing the socket timeout configuration for a client. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt similarity index 78% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt index a185de00..cbc2cf56 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/trait/configuration/ToClientConfigurationTrait.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.v2.core.trait.configuration +package com.expediagroup.sdk.core.trait.configuration -import com.expediagroup.sdk.v2.core.trait.Trait +import com.expediagroup.sdk.core.trait.Trait /** * An interface representing a trait that can be converted to a `ClientConfigurationTrait`. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt index 3bb3ab0f..d76b10be 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -1,6 +1,9 @@ package com.expediagroup.sdk.lodgingconnectivity.configuration -import com.expediagroup.sdk.core.configuration.ExpediaGroupClientConfiguration +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException +import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy +import com.expediagroup.sdk.core.configuration.ExpediaGroupDefaultClientConfiguration +import com.expediagroup.sdk.core.configuration.FullClientConfiguration /** * A configuration class that holds the necessary credentials and settings for API clients. @@ -27,7 +30,9 @@ data class ClientConfiguration( val connectionTimeout: Long? = null, val socketTimeout: Long? = null, val maskedLoggingHeaders: Set? = null, - val maskedLoggingBodyFields: Set? = null + val maskedLoggingBodyFields: Set? = null, + val maxConnTotal: Int? = null, + val maxConnPerRoute: Int? = null ) { /** @@ -42,6 +47,8 @@ data class ClientConfiguration( private var socketTimeout: Long? = null private var maskedLoggingHeaders: Set? = null private var maskedLoggingBodyFields: Set? = null + private var maxConnTotal: Int? = null + private var maxConnPerRoute: Int? = null /** * Sets the API key. @@ -107,6 +114,14 @@ data class ClientConfiguration( this.maskedLoggingBodyFields = maskedLoggingBodyFields } + fun maxConnTotal(maxConnTotal: Int) = apply { + this.maxConnTotal = maxConnTotal + } + + fun maxConnPerRoute(maxConnPerRoute: Int) = apply { + this.maxConnPerRoute = maxConnPerRoute + } + /** * Builds and returns the `ClientConfiguration` instance. * @return The configured `ClientConfiguration`. @@ -120,7 +135,9 @@ data class ClientConfiguration( connectionTimeout, socketTimeout, maskedLoggingHeaders, - maskedLoggingBodyFields + maskedLoggingBodyFields, + maxConnTotal, + maxConnPerRoute, ) } } @@ -130,23 +147,49 @@ data class ClientConfiguration( fun builder(): Builder = Builder() } - internal fun toExpediaGroupClientConfiguration( + internal fun toFullClientConfiguration( endpointProvider: (ClientEnvironment) -> String, authEndpointProvider: (ClientEnvironment) -> String, defaultEnvironment: ClientEnvironment = ClientEnvironment.PROD - ): ExpediaGroupClientConfiguration { + ): FullClientConfiguration { val environment = this.environment ?: defaultEnvironment - return ExpediaGroupClientConfiguration( - key = this.key, - secret = this.secret, - endpoint = endpointProvider(environment), - authEndpoint = authEndpointProvider(environment), - requestTimeout = this.requestTimeout, - connectionTimeout = this.connectionTimeout, - socketTimeout = this.socketTimeout, - maskedLoggingHeaders = this.maskedLoggingHeaders, - maskedLoggingBodyFields = this.maskedLoggingBodyFields - ) + return object : FullClientConfiguration { + override fun getKey(): String = + key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") + + override fun getSecret(): String = + secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") + + override fun getEndpoint(): String = + endpointProvider(environment) + + override fun getAuthEndpoint(): String = + authEndpointProvider(environment) + + override fun getMaskedLoggingHeaders(): Set = + maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() + + override fun getMaskedLoggingBodyFields(): Set = + maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() + + override fun getRequestTimeout(): Long = + requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() + + override fun getSocketTimeout(): Long = + socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() + + override fun getConnectionTimeout(): Long = + connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() + + override fun getAuthenticationStrategy(): AuthenticationStrategy = + ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() + + override fun getMaxConnectionsTotal(): Int = + maxConnTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() + + override fun getMaxConnectionsPerRoute(): Int = + maxConnPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() + } } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt index 07823c7e..1e4ae899 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt @@ -35,4 +35,3 @@ enum class ClientEnvironment { */ SANDBOX_TEST } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt index 66e0a32a..4a8367bb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt @@ -19,6 +19,11 @@ enum class SandboxDataManagementClientEndpoint(val url: String) { SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql") } +enum class FileManagementClientEndpoint(val url: String) { + PROD("https://api.expediagroup.com/supply-lodging/v1/files"), + TEST("https://test-api.expediagroup.com/supply-lodging/v1/files") +} + enum class AuthEndpoint(val url: String) { PROD("https://api.expediagroup.com/identity/oauth2/v3/token/"), TEST("https://test-api.expediagroup.com/identity/oauth2/v3/token/"), @@ -75,6 +80,14 @@ object EndpointProvider { } } + fun getFileManagementClientEndpoint(environment: ClientEnvironment): String { + return try { + FileManagementClientEndpoint.valueOf(environment.name).url + } catch (e: IllegalArgumentException) { + throw IllegalArgumentException("Unsupported environment [$environment] for FileManagementClient") + } + } + fun getAuthEndpoint(environment: ClientEnvironment): String { return try { AuthEndpoint.valueOf(environment.name).url @@ -87,4 +100,4 @@ object EndpointProvider { ) } } -} \ No newline at end of file +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt similarity index 73% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt index 757d268a..c2352d20 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/client/FileManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt @@ -1,16 +1,19 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client - -import com.expediagroup.sdk.v2.core.client.SdkClient -import com.expediagroup.sdk.v2.core.configuration.DefaultClientBuilder -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.FileUploadRequest -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.Upload201Response -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileDownloadOperation -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileDownloadOperationParams -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileUploadOperation -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations.FileUploadOperationParams -import java.io.* +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.client + +import com.expediagroup.sdk.core.client.SdkClient +import com.expediagroup.sdk.core.configuration.DefaultClientBuilder +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.FileUploadRequest +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.Upload201Response +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileDownloadOperation +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileDownloadOperationParams +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileUploadOperation +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileUploadOperationParams +import java.io.IOException +import java.io.OutputStream +import java.io.InputStream +import java.io.File class FileManagementClient( configuration: ClientConfiguration @@ -123,7 +126,7 @@ class FileManagementClient( @JvmStatic fun builder(): Builder = Builder() - class Builder: DefaultClientBuilder() { + class Builder : DefaultClientBuilder() { private val configurationBuilder = ClientConfiguration.builder() override fun build(): FileManagementClient = diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt similarity index 76% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt index f8f41029..60dd2a1a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models import java.io.File import java.io.InputStream diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt similarity index 97% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt index 22a800a1..7c2192b0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/GenericError.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models /* @@ -90,4 +90,3 @@ data class GenericError( } } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt index 038aa226..27cd4871 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/Upload201Response.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models /* @@ -69,4 +69,3 @@ data class Upload201Response( } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt similarity index 91% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt index 56d5cb2a..fe74cbb7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt @@ -15,7 +15,7 @@ */ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception /** * An entity to represent a constraint violation of a property. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt similarity index 93% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt index 04265239..7500d625 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt @@ -15,7 +15,7 @@ */ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupClientException diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt similarity index 80% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt index 314b7326..2aecc192 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt @@ -1,8 +1,8 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations import com.expediagroup.sdk.core.model.Nothing -import com.expediagroup.sdk.v2.core.model.Operation -import com.expediagroup.sdk.v2.core.model.OperationParams +import com.expediagroup.sdk.core.model.Operation +import com.expediagroup.sdk.core.model.OperationParams class FileDownloadOperation( params: FileDownloadOperationParams, @@ -29,4 +29,4 @@ class FileDownloadOperationParams( override fun getPathParams(): Map = buildMap { put("id", id) } -} \ No newline at end of file +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt similarity index 83% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt index 41340b76..83bc3d31 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt @@ -1,15 +1,20 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.operations +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations -import com.expediagroup.sdk.v2.core.http.BlobTypeDetector -import com.expediagroup.sdk.v2.core.model.OperationParams -import com.expediagroup.sdk.v2.core.model.Operation -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.FileUploadRequest -import com.google.api.client.http.* +import com.expediagroup.sdk.core.http.BlobTypeDetector +import com.expediagroup.sdk.core.model.OperationParams +import com.expediagroup.sdk.core.model.Operation +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.FileUploadRequest +import com.google.api.client.http.FileContent +import com.google.api.client.http.HttpContent +import com.google.api.client.http.HttpHeaders +import com.google.api.client.http.HttpMediaType +import com.google.api.client.http.InputStreamContent +import com.google.api.client.http.MultipartContent import com.google.api.client.http.MultipartContent.Part import org.apache.tika.mime.MimeTypes import java.io.File import java.io.InputStream -import java.util.* +import java.util.UUID class FileUploadOperation( @@ -89,4 +94,4 @@ class FileUploadOperationParams( override fun getPathParams(): Map = emptyMap() -} \ No newline at end of file +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt similarity index 85% rename from code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt index da1fca65..86fd64dd 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt @@ -15,10 +15,10 @@ */ -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.validation +package com.expediagroup.sdk.lodgingconnectivity.filemanagement.validation -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolation -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolationException +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolation +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolationException import javax.validation.ConstraintViolation import javax.validation.Validation import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt index 4a663b95..26864317 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt @@ -16,40 +16,40 @@ package com.expediagroup.sdk.lodgingconnectivity.graphql -import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.api.Mutation import com.apollographql.apollo.api.Query -import com.apollographql.ktor.http.KtorHttpEngine -import com.expediagroup.sdk.core.client.ExpediaGroupClient -import com.expediagroup.sdk.core.configuration.ExpediaGroupClientConfiguration +import com.apollographql.java.client.ApolloClient import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException -import io.ktor.client.statement.HttpResponse -import kotlinx.coroutines.runBlocking +import com.expediagroup.sdk.core.configuration.DefaultClientBuilder +import com.expediagroup.sdk.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.core.client.ApiClientApolloHttpEngine +import com.expediagroup.sdk.core.client.util.createApiClient +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import java.util.concurrent.CompletableFuture -/** - * An internal base implementation of a GraphQL client for executing GraphQL queries, mutations, and subscriptions. - * - * This class integrates the Apollo GraphQL client with a custom `ExpediaGroupClient` for handling HTTP communication - * and error management. It provides a foundation for more specific client implementations by executing operations - * with error handling. - * - * @param config The configuration for the `ExpediaGroupClient` - */ -internal class BaseGraphQLClient(config: ExpediaGroupClientConfiguration) : GraphQLExecutor { +class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecutor { + private val engine: ApiClientApolloHttpEngine = ApiClientApolloHttpEngine( + createApiClient( + configuration = configuration + ) + ) - // Custom client for handling HTTP requests and responses. - private val expediaGroupClient = - object : ExpediaGroupClient(clientConfiguration = config, namespace = "lodging-connectivity-sdk") { - override suspend fun throwServiceException(response: HttpResponse, operationId: String) { - throw ExpediaGroupServiceException("Service error occurred for operation $operationId.\nResponse: $response") - } - } + companion object { + @JvmStatic + fun builder() = ClientConfiguration.builder() + } private val apolloClient: ApolloClient = ApolloClient.Builder() - .serverUrl(config.endpoint!!) - .httpEngine(KtorHttpEngine(expediaGroupClient.httpClient)) + .serverUrl(configuration.getEndpoint()) + .httpEngine(engine) .build() + class Builder() : DefaultClientBuilder() { + override fun build(): BaseGraphQLClient { + return BaseGraphQLClient(this.buildConfiguration()) + } + } + /** * Executes a GraphQL query and returns the result. * @@ -57,19 +57,34 @@ internal class BaseGraphQLClient(config: ExpediaGroupClientConfiguration) : Grap * @return The result of the query execution, with errors handled. * @throws ExpediaGroupServiceException If the query execution returns errors. */ - override fun execute(query: Query): T { - return runBlocking { - apolloClient.query(query).execute().apply { - if (exception != null) { - throw ExpediaGroupServiceException(exception?.message) - } - if (hasErrors()) { - throw ExpediaGroupServiceException(errors.toString()) + override fun executeAsync(query: Query): CompletableFuture { + val promise: CompletableFuture = CompletableFuture() + + apolloClient.query(query).enqueue { response -> + try { + if (response.hasErrors()) { + // Complete exceptionally if there are GraphQL errors + promise.completeExceptionally(ExpediaGroupServiceException(message = response.errors.toString())) + } else if (response.exception != null) { + // Complete exceptionally if there is a network or other exception + promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) + } else { + // Complete normally with the response data if no errors or exceptions + promise.complete(response.dataAssertNoErrors) } - }.dataAssertNoErrors + } catch (e: Exception) { + // Handle unexpected exceptions during callback execution + promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) + } } + + return promise } + override fun execute(query: Query): T = + executeAsync(query).get() + + /** * Executes a GraphQL mutation and returns the result. * @@ -77,17 +92,30 @@ internal class BaseGraphQLClient(config: ExpediaGroupClientConfiguration) : Grap * @return The result of the mutation execution, with errors handled. * @throws ExpediaGroupServiceException If the mutation execution returns errors. */ - override fun execute(mutation: Mutation): T { - return runBlocking { - apolloClient.mutation(mutation).execute().apply { - if (exception != null) { - throw ExpediaGroupServiceException(exception?.message) - } - if (hasErrors()) { - throw ExpediaGroupServiceException(errors.toString()) + override fun executeAsync(mutation: Mutation): CompletableFuture { + val promise: CompletableFuture = CompletableFuture() + + apolloClient.mutation(mutation).enqueue { response -> + try { + if (response.hasErrors()) { + // Complete exceptionally if there are GraphQL errors + promise.completeExceptionally(ExpediaGroupServiceException(response.errors.toString())) + } else if (response.exception != null) { + // Complete exceptionally if there is a network or other exception + promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) + } else { + // Complete normally with the response data if no errors or exceptions + promise.complete(response.dataAssertNoErrors) } - }.dataAssertNoErrors + } catch (e: Exception) { + // Handle unexpected exceptions during callback execution + promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) + } } + + return promise } -} + override fun execute(mutation: Mutation): T = + executeAsync(mutation).get() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt index 6dfb1613..5a09ffcf 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt @@ -18,34 +18,11 @@ package com.expediagroup.sdk.lodgingconnectivity.graphql import com.apollographql.apollo.api.Mutation import com.apollographql.apollo.api.Query -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import java.util.concurrent.CompletableFuture -/** - * An interface for executing GraphQL operations, including queries, mutations, and subscriptions. - * - * Implementers of this interface are responsible for executing GraphQL operations and returning the results, - * while handling any errors that may occur during the execution. - */ interface GraphQLExecutor { - - /** - * Executes a GraphQL query and returns the result. - * - * @param query The GraphQL query to execute. - * @return The result of the query execution. - * @param The type of data returned by the query, extending `Query.Data`. - * @throws ExpediaGroupServiceException If the query execution returns errors. - */ + fun executeAsync(query: Query): CompletableFuture + fun executeAsync(mutation: Mutation): CompletableFuture fun execute(query: Query): T - - /** - * Executes a GraphQL mutation and returns the result. - * - * @param mutation The GraphQL mutation to execute. - * @return The result of the mutation execution. - * @param The type of data returned by the mutation, extending `Mutation.Data`. - * @throws ExpediaGroupServiceException If the mutation execution returns errors. - */ fun execute(mutation: Mutation): T } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt index 0f961284..b1bf39e7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt @@ -40,5 +40,3 @@ object DateTimeAdapter : Adapter { } } } - - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt index cf03852f..570cbf16 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt @@ -40,4 +40,3 @@ object ZoneDateTimeAdapter : Adapter { } } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt index b45b7b8f..b7648c47 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt @@ -16,8 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.graphql.payment -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor @@ -42,10 +42,10 @@ import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor * ) * ``` */ -class PaymentClient(config: ClientConfiguration) : +class PaymentClient(config: ClientConfiguration): GraphQLExecutor by BaseGraphQLClient( - config.toExpediaGroupClientConfiguration( + config.toFullClientConfiguration( endpointProvider = EndpointProvider::getPaymentClientEndpoint, authEndpointProvider = EndpointProvider::getAuthEndpoint - ) + ), ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt index 56033c9f..8b1d4376 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt @@ -16,9 +16,9 @@ package com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox +import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor @@ -43,11 +43,11 @@ import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor * ) * ``` */ -class SandboxDataManagementClient(config: ClientConfiguration) : +class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLExecutor by BaseGraphQLClient( - config.toExpediaGroupClientConfiguration( + config.toFullClientConfiguration( endpointProvider = EndpointProvider::getSandboxDataManagementClientEndpoint, authEndpointProvider = EndpointProvider::getAuthEndpoint, defaultEnvironment = ClientEnvironment.SANDBOX_PROD - ) + ), ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt index 3f370626..c97e057e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt @@ -16,8 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.graphql.supply -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor @@ -47,9 +47,9 @@ import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor * ``` */ class SupplyClient(config: ClientConfiguration) : - GraphQLExecutor by BaseGraphQLClient( - config.toExpediaGroupClientConfiguration( - endpointProvider = EndpointProvider::getSupplyClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint - ) - ) + GraphQLExecutor by BaseGraphQLClient( + config.toFullClientConfiguration( + endpointProvider = EndpointProvider::getSupplyClientEndpoint, + authEndpointProvider = EndpointProvider::getAuthEndpoint + ), + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt deleted file mode 100644 index c931a205..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Authentication.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.expediagroup.sdk.v2.core.constant - -object Authentication { - const val BEARER_EXPIRY_MARGIN_IN_SECONDS: Long = 10 -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt deleted file mode 100644 index 2618a512..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Constant.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -import io.ktor.client.plugins.HttpTimeout - -internal object Constant { - const val EMPTY_STRING = "" - const val NEWLINE = "\n" - const val COMMA_SPACE = ", " - const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" - const val TEN_SECONDS_IN_MILLIS = 10_0000L - const val FIFTEEN_SECONDS_IN_MILLIS = 150_000L - const val INFINITE_TIMEOUT = HttpTimeout.INFINITE_TIMEOUT_MS - - private const val SUCCESSFUL_STATUS_CODES_RANGE_START = 200 - private const val SUCCESSFUL_STATUS_CODES_RANGE_END = 299 - val SUCCESSFUL_STATUS_CODES_RANGE: IntRange = SUCCESSFUL_STATUS_CODES_RANGE_START..SUCCESSFUL_STATUS_CODES_RANGE_END - - const val MAX_CONNECTIONS_TOTAL: Int = 500 - const val MAX_CONNECTIONS_PER_ROUTE: Int = 100 -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt deleted file mode 100644 index b7671c48..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/ExceptionMessage.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object ExceptionMessage { - const val AUTHENTICATION_FAILURE = "Unable to authenticate" - - const val AUTHENTICATION_NOT_CONFIGURED_FOR_CLIENT = "Authentication is not configured" - - const val LOGGING_MASKED_FIELDS_NOT_CONFIGURED_FOR_CLIENT = "Logging masked fields is not configured" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt deleted file mode 100644 index 3ae20666..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderKey.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object HeaderKey { - const val PAGINATION_TOTAL_RESULTS = "pagination-total-results" - - const val LINK = "link" - - const val TRANSACTION_ID = "transaction-id" - - const val X_SDK_TITLE = "x-sdk-title" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt deleted file mode 100644 index e5627a05..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/HeaderValue.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object HeaderValue { - const val GZIP = "gzip" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt deleted file mode 100644 index 4536f84d..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/Key.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object Key { - const val CLIENT_KEY = "client_key" - - const val CLIENT_SECRET = "client_secret" - - const val ENDPOINT = "endpoint" - - const val AUTH_ENDPOINT = "auth_endpoint" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt deleted file mode 100644 index 479cbed7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LogMaskingFields.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -import com.google.auth.http.AuthHttpConstants - -internal data object LogMaskingFields { - val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(AuthHttpConstants.AUTHORIZATION) - val DEFAULT_MASKED_BODY_FIELDS: Set = - setOf( - "cvv", - "pin", - "card_cvv", - "card_cvv2", - "card_number", - "access_token", - "security_code", - "account_number", - "card_avs_response", - "card_cvv_response", - "card_cvv2_response" - ) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt deleted file mode 100644 index f1eadeee..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggerName.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object LoggerName { - const val REQUEST_BODY_LOGGER: String = "RequestBodyLogger" - const val RESPONSE_BODY_LOGGER: String = "ResponseBodyLogger" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt deleted file mode 100644 index 04928f2c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/constant/LoggingMessage.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.constant - -internal object LoggingMessage { - const val LOGGING_PREFIX = "ExpediaSDK:" - - const val TOKEN_RENEWAL_IN_PROGRESS = "Renewing token" - - const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" - - const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" - - const val OMITTED = "<-- omitted -->" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt deleted file mode 100644 index ef1f6786..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Headers.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model - -import com.expediagroup.sdk.v2.core.constant.HeaderKey -import io.ktor.http.* - -/** Get transaction id from headers. */ -fun Headers.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) - -/** Get transaction id from headers builder. */ -fun HeadersBuilder.getTransactionId(): String? = get(HeaderKey.TRANSACTION_ID) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt deleted file mode 100644 index 0daa0eff..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Nothing.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model - -/** - * A representation of nothingness. Philosophers have debated the existence of nothing for centuries, but we have finally found it. - */ -data object Nothing diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt deleted file mode 100644 index 7b85c121..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Properties.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model - -import java.io.BufferedReader -import java.io.InputStreamReader -import java.net.URL - -/** A model of "*.properties" file with some handy methods. */ -class Properties(private val data: Map) { - companion object { - /** Creates a new SdkProperties with the given data. */ - fun from(path: URL) = - Properties( - java.util.Properties().apply { - load(BufferedReader(InputStreamReader(path.openStream()))) - }.map { it.key.toString() to it.value.toString() }.toMap() - ) - } - - /** Returns the data for a given [key]. */ - operator fun get(key: String): String? = data[key] -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt deleted file mode 100644 index b266503e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/Response.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:Suppress("unused") - -package com.expediagroup.sdk.v2.core.model - -import java.util.stream.Collectors -import kotlin.collections.Map.Entry - -/** - * A Generic Response to represent the response from a service call. - * - * @property statusCode The HTTP status code of the response - * @property data The body of the response - * @property headers The headers of the response - */ -@Suppress("MemberVisibilityCanBePrivate") -open class Response( - val statusCode: Int, - val data: T, - val headers: Map> -) { - constructor(statusCode: Int, data: T, headers: Set>>) : this( - statusCode, - data, - toHeadersMap(headers) - ) - - companion object { - @JvmStatic - fun toHeadersMap(headers: Set>>): Map> = - headers.stream().collect( - Collectors.toMap( - Entry>::key, - Entry>::value - ) - ) - } - - override fun toString() = "Response(statusCode=$statusCode, data=$data, headers=$headers)" - - @Deprecated("Use getData() instead", replaceWith = ReplaceWith("getData()")) - fun getBody() = data -} - -class EmptyResponse( - statusCode: Int, - headers: Map> -) : Response(statusCode, Nothing, headers) { - constructor(statusCode: Int, headers: Set>>) : this(statusCode, toHeadersMap(headers)) - - override fun toString(): String = "EmptyResponse(statusCode=$statusCode, headers=$headers)" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt deleted file mode 100644 index 879411a7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/TransactionId.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model - -import java.util.UUID - -class TransactionId { - private var transactionId: UUID = UUID.randomUUID() - - fun peek(): UUID { - return transactionId - } - - fun dequeue(): UUID { - return transactionId.also { transactionId = UUID.randomUUID() } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt deleted file mode 100644 index fa010e26..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/ExpediaGroupException.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception - -/** - * A base exception for all ExpediaGroup exceptions. - * - * @param message An optional error message. - * @param cause An optional cause of the error. - */ -open class ExpediaGroupException( - message: String? = null, - cause: Throwable? = null -) : RuntimeException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt deleted file mode 100644 index 988da1f4..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupClientException.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.client - -import com.expediagroup.sdk.v2.core.model.exception.ExpediaGroupException - -/** - * An exception that is thrown when a client error occurs. - * - * @param message An optional message. - * @param cause An optional cause. - */ -open class ExpediaGroupClientException( - message: String? = null, - cause: Throwable? = null -) : ExpediaGroupException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt deleted file mode 100644 index 73dd88e0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupConfigurationException.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.client - -/** - * An exception that is thrown when a configuration error occurs. - * - * @param message An optional error message. - * @param cause An optional cause of the error. - */ -class ExpediaGroupConfigurationException( - message: String? = null, - cause: Throwable? = null -) : ExpediaGroupClientException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt deleted file mode 100644 index 86f9aad0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.client - -/** - * Thrown to indicate that one or more passed field names are invalid. - * - * @param invalidFields the names of the invalid fields. - */ -class ExpediaGroupInvalidFieldNameException(invalidFields: Collection) : - ExpediaGroupClientException( - "All fields names must contain only alphanumeric characters in addition to - and _ but found [${ - invalidFields.joinToString( - "," - ) - }]" - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt deleted file mode 100644 index d7626506..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupAuthException.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.service - -import io.ktor.http.* - -/** - * An exception that is thrown when an authentication error occurs. - * - * @param message The error message. - * @param cause The cause of the error. - * @param transactionId The transaction-id of the auth request. - */ -class ExpediaGroupAuthException( - message: String? = null, - cause: Throwable? = null, - transactionId: String? = null -) : ExpediaGroupServiceException(message, cause, transactionId) { - /** - * An exception that is thrown when an authentication error occurs. - * - * @param errorCode The HTTP status code of the error. - * @param message The error message. - */ - constructor( - errorCode: HttpStatusCode, - message: String, - transactionId: String? - ) : this(message = "[${errorCode.value}] $message", transactionId = transactionId) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt deleted file mode 100644 index e51c1ea1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.service - -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupApiException - -class ExpediaGroupServiceDefaultErrorException(code: Int, override val errorObject: String, transactionId: String?) : - ExpediaGroupApiException(code, errorObject, transactionId) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt deleted file mode 100644 index 4a26fe4a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/core/model/exception/service/ExpediaGroupServiceException.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.v2.core.model.exception.service - -import com.expediagroup.sdk.v2.core.model.exception.ExpediaGroupException - -/** - * An exception that is thrown when a service error occurs. - * - * @param message An optional error message. - * @param cause An optional cause of the error. - */ -open class ExpediaGroupServiceException( - message: String? = null, - cause: Throwable? = null, - val transactionId: String? = null -) : ExpediaGroupException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt deleted file mode 100644 index 67006091..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientConfiguration.kt +++ /dev/null @@ -1,195 +0,0 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.configuration - -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -import com.expediagroup.sdk.v2.core.authentication.strategy.AuthenticationStrategy -import com.expediagroup.sdk.v2.core.configuration.ExpediaGroupDefaultClientConfiguration -import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration - -/** - * A configuration class that holds the necessary credentials and settings for API clients. - * - * This class is used to configure SDK clients by providing essential - * details such as API keys, environment, timeouts, and logging settings. - * - * It also provides a fluent `Builder` pattern for easy creation of configuration instances. - * - * @property key The API key used for authentication. - * @property secret The API secret used for authentication. - * @property environment The environment in which the API client will operate (e.g., production or test). - * @property requestTimeout The request timeout duration in milliseconds (optional). - * @property connectionTimeout The connection timeout duration in milliseconds (optional). - * @property socketTimeout The socket timeout duration in milliseconds (optional). - * @property maskedLoggingHeaders A set of HTTP headers whose values should be masked in logs (optional). - * @property maskedLoggingBodyFields A set of fields in the request body whose values should be masked in logs (optional). - */ -data class ClientConfiguration( - val key: String?, - val secret: String?, - val environment: ClientEnvironment?, - val requestTimeout: Long? = null, - val connectionTimeout: Long? = null, - val socketTimeout: Long? = null, - val maskedLoggingHeaders: Set? = null, - val maskedLoggingBodyFields: Set? = null, - val maxConnTotal: Int? = null, - val maxConnPerRoute: Int? = null -) { - - /** - * A builder for creating `ClientConfiguration` instances. - */ - class Builder { - private var key: String? = null - private var secret: String? = null - private var environment: ClientEnvironment? = null - private var requestTimeout: Long? = null - private var connectionTimeout: Long? = null - private var socketTimeout: Long? = null - private var maskedLoggingHeaders: Set? = null - private var maskedLoggingBodyFields: Set? = null - private var maxConnTotal: Int? = null - private var maxConnPerRoute: Int? = null - - /** - * Sets the API key. - * @param key The API key to use. - */ - fun key(key: String) = apply { - this.key = key - } - - /** - * Sets the API secret. - * @param secret The API secret to use. - */ - fun secret(secret: String) = apply { - this.secret = secret - } - - /** - * Sets the environment (e.g., production, test, or sandbox). - * @param environment The `ClientEnvironment` to use. - */ - fun environment(environment: ClientEnvironment) = apply { - this.environment = environment - } - - /** - * Sets the request timeout in milliseconds. - * @param requestTimeout The request timeout duration. - */ - fun requestTimeout(requestTimeout: Long) = apply { - this.requestTimeout = requestTimeout - } - - /** - * Sets the connection timeout in milliseconds. - * @param connectionTimeout The connection timeout duration. - */ - fun connectionTimeout(connectionTimeout: Long) = apply { - this.connectionTimeout = connectionTimeout - } - - /** - * Sets the socket timeout in milliseconds. - * @param socketTimeout The socket timeout duration. - */ - fun socketTimeout(socketTimeout: Long) = apply { - this.socketTimeout = socketTimeout - } - - /** - * Sets the headers whose values should be masked in logs. - * @param maskedLoggingHeaders A set of HTTP headers to mask in logs. - */ - fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { - this.maskedLoggingHeaders = maskedLoggingHeaders - } - - /** - * Sets the body fields whose values should be masked in logs. - * @param maskedLoggingBodyFields A set of fields in the request body to mask in logs. - */ - fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { - this.maskedLoggingBodyFields = maskedLoggingBodyFields - } - - fun maxConnTotal(maxConnTotal: Int) = apply { - this.maxConnTotal = maxConnTotal - } - - fun maxConnPerRoute(maxConnPerRoute: Int) = apply { - this.maxConnPerRoute = maxConnPerRoute - } - - /** - * Builds and returns the `ClientConfiguration` instance. - * @return The configured `ClientConfiguration`. - */ - fun build(): ClientConfiguration { - return ClientConfiguration( - key, - secret, - environment, - requestTimeout, - connectionTimeout, - socketTimeout, - maskedLoggingHeaders, - maskedLoggingBodyFields, - maxConnTotal, - maxConnPerRoute, - ) - } - } - - companion object { - @JvmStatic - fun builder(): Builder = Builder() - } - - internal fun toFullClientConfiguration( - endpointProvider: (ClientEnvironment) -> String, - authEndpointProvider: (ClientEnvironment) -> String, - defaultEnvironment: ClientEnvironment = ClientEnvironment.PROD - ): FullClientConfiguration { - val environment = this.environment ?: defaultEnvironment - - return object : FullClientConfiguration { - override fun getKey(): String = - key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") - - override fun getSecret(): String = - secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") - - override fun getEndpoint(): String = - endpointProvider(environment) - - override fun getAuthEndpoint(): String = - authEndpointProvider(environment) - - override fun getMaskedLoggingHeaders(): Set = - maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() - - override fun getMaskedLoggingBodyFields(): Set = - maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() - - override fun getRequestTimeout(): Long = - requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() - - override fun getSocketTimeout(): Long = - socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() - - override fun getConnectionTimeout(): Long = - connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() - - override fun getAuthenticationStrategy(): AuthenticationStrategy = - ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() - - override fun getMaxConnectionsTotal(): Int = - maxConnTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() - - override fun getMaxConnectionsPerRoute(): Int = - maxConnPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt deleted file mode 100644 index 3513b096..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/ClientEnvironment.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.configuration - -/** - * An enumeration representing the possible environments for the API clients. - * - * This enum defines the different environments in which an API client can operate, including - * production, test, and sandbox environments. It is used in the configuration to determine - * which endpoints and settings to apply based on the environment selected. - */ -enum class ClientEnvironment { - - /** - * Represents the production environment. - * Clients in this environment interact with the live, production-grade API. - */ - PROD, - - /** - * Represents the test environment. - * Clients in this environment interact with EG internal test/lab API. - */ - TEST, - - /** - * Represents the sandbox version of the production environment. - * - * This environment is only available for `SupplyClient` - */ - SANDBOX_PROD, - - /** - * Represents the sandbox version of the EG internal test/lab environment. - * - * This environment is only available for `SupplyClient` - */ - SANDBOX_TEST -} - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt deleted file mode 100644 index ab259313..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/configuration/EndpointProvider.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.expediagroup.sdk.v2.lodgingconnectivity.configuration - -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException - -enum class SupplyClientEndpoint(val url: String) { - PROD("https://api.expediagroup.com/supply/lodging/graphql"), - TEST("https://test-api.expediagroup.com/supply/lodging/graphql"), - SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging/graphql"), - SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging/graphql") -} - -enum class PaymentClientEndpoint(val url: String) { - PROD("https://api.expediagroup.com/supply/payments/graphql"), - TEST("https://test-api.expediagroup.com/supply/payments/graphql") -} - -enum class SandboxDataManagementClientEndpoint(val url: String) { - SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql"), - SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql") -} - -enum class FileManagementClientEndpoint(val url: String) { - PROD("https://api.expediagroup.com/supply-lodging/v1/files"), - TEST("https://test-api.expediagroup.com/supply-lodging/v1/files") -} - -enum class AuthEndpoint(val url: String) { - PROD("https://api.expediagroup.com/identity/oauth2/v3/token/"), - TEST("https://test-api.expediagroup.com/identity/oauth2/v3/token/"), - SANDBOX_PROD("https://api.expediagroup.com/identity/oauth2/v3/token/"), - SANDBOX_TEST("https://test-api.expediagroup.com/identity/oauth2/v3/token/") -} - -/** - * An internal utility object for providing API endpoints based on the environment. - * - * This class contains methods to retrieve the correct endpoint for different clients - * (e.g., Supply, Payment, Sandbox, File Management, and Authentication) based on the - * provided `ClientEnvironment`. - * - * If an unsupported environment is passed, an `IllegalArgumentException` is thrown. - */ -object EndpointProvider { - fun getSupplyClientEndpoint(environment: ClientEnvironment): String { - return try { - SupplyClientEndpoint.valueOf(environment.name).url - } catch (e: IllegalArgumentException) { - throw ExpediaGroupConfigurationException( - """ - Unsupported environment [$environment] for SupplyClient. - Supported environments are [${SupplyClientEndpoint.entries.joinToString(", ") }] - """ - ) - } - } - - fun getPaymentClientEndpoint(environment: ClientEnvironment): String { - return try { - PaymentClientEndpoint.valueOf(environment.name).url - } catch (e: IllegalArgumentException) { - throw ExpediaGroupConfigurationException( - """ - Unsupported environment [$environment] for PaymentClient. - Supported environments are [${PaymentClientEndpoint.entries.joinToString(", ") }] - """ - ) - } - } - - fun getSandboxDataManagementClientEndpoint(environment: ClientEnvironment): String { - return try { - SandboxDataManagementClientEndpoint.valueOf(environment.name).url - } catch (e: IllegalArgumentException) { - throw ExpediaGroupConfigurationException( - """ - Unsupported environment [$environment] for SandboxDataManagementClient. - Supported environments are [${SandboxDataManagementClientEndpoint.entries.joinToString(", ") }] - """ - ) - } - } - - fun getFileManagementClientEndpoint(environment: ClientEnvironment): String { - return try { - FileManagementClientEndpoint.valueOf(environment.name).url - } catch (e: IllegalArgumentException) { - throw IllegalArgumentException("Unsupported environment [$environment] for FileManagementClient") - } - } - - fun getAuthEndpoint(environment: ClientEnvironment): String { - return try { - AuthEndpoint.valueOf(environment.name).url - } catch (e: IllegalArgumentException) { - throw ExpediaGroupConfigurationException( - """ - Unsupported environment [$environment] for Authentication. - Supported environments are [${AuthEndpoint.entries.joinToString(", ") }] - """ - ) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt deleted file mode 100644 index e2471771..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/filemanagement/models/exception/ApiException.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.exception - -import com.expediagroup.sdk.core.model.getTransactionId -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupApiException -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceDefaultErrorException -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.models.* -import io.ktor.client.call.* -import io.ktor.client.statement.* -import kotlinx.coroutines.runBlocking - -internal open class HttpStatusCodeRange( - private val statusCode: String, - val getException: (HttpResponse) -> ExpediaGroupApiException -) : Comparable { - open fun matches(statusCode: String): Boolean = if (isRangeDefinition()) this.statusCode.first() == statusCode.first() else this.statusCode == statusCode - open fun isRangeDefinition(): Boolean = statusCode.matches(Regex("^[1-5]XX$")) - override fun compareTo(other: HttpStatusCodeRange): Int = (if (this.isRangeDefinition()) 1 else 0).compareTo(if (other.isRangeDefinition()) 1 else 0) -} - -internal object DefaultHttpStatusCodeRange : HttpStatusCodeRange( - "DefaultHttpStatusCodeRange", - { ExpediaGroupServiceDefaultErrorException(it.status.value, runBlocking { it.bodyAsText() }, it.request.headers.getTransactionId()) } -) { - override fun matches(statusCode: String): Boolean = true - override fun isRangeDefinition(): Boolean = true -} - -internal object ErrorObjectMapper { - private val defaultHttpStatusCodeRanges = listOf(DefaultHttpStatusCodeRange) - private val httpStatusCodeRanges: Map< String, List< HttpStatusCodeRange > > = mapOf( - Pair( - "download", - listOf( - HttpStatusCodeRange("400") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("401") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("403") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("500") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("503") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - DefaultHttpStatusCodeRange - ) - ), - Pair( - "upload", - listOf( - HttpStatusCodeRange("400") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("401") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("403") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("500") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - HttpStatusCodeRange("503") { ExpediaGroupApiGenericErrorException(it.status.value, fetchErrorObject(it) as GenericError, it.headers.getTransactionId()) }, - DefaultHttpStatusCodeRange - ) - ) - ) - - fun process(httpResponse: HttpResponse, operationId: String): ExpediaGroupApiException = - httpStatusCodeRanges.getOrDefault(operationId, defaultHttpStatusCodeRanges).filter { it.matches(httpResponse.status.value.toString()) }.min().getException(httpResponse) - - private inline fun fetchErrorObject(httpResponse: HttpResponse): T = runBlocking { - runCatching { httpResponse.body() }.getOrElse { throw ExpediaGroupServiceDefaultErrorException(httpResponse.status.value, httpResponse.bodyAsText(), httpResponse.request.headers.getTransactionId()) } - } -} - - class ExpediaGroupApiGenericErrorException(code: Int, override val errorObject: GenericError, transactionId: String?) : ExpediaGroupApiException(code, errorObject, transactionId) - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt deleted file mode 100644 index aa9ee0fb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/BaseGraphQLClient.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql - -import com.apollographql.apollo.api.Mutation -import com.apollographql.apollo.api.Query -import com.apollographql.java.client.ApolloClient -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException -import com.expediagroup.sdk.v2.core.configuration.DefaultClientBuilder -import com.expediagroup.sdk.v2.core.configuration.FullClientConfiguration -import com.expediagroup.sdk.v2.core.client.ApiClientApolloHttpEngine -import com.expediagroup.sdk.v2.core.client.util.createApiClient -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration -import java.util.concurrent.CompletableFuture - -class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecutor { - private val engine: ApiClientApolloHttpEngine = ApiClientApolloHttpEngine( - createApiClient( - configuration = configuration - ) - ) - - companion object { - @JvmStatic - fun builder() = ClientConfiguration.builder() - } - - private val apolloClient: ApolloClient = ApolloClient.Builder() - .serverUrl(configuration.getEndpoint()) - .httpEngine(engine) - .build() - - class Builder() : DefaultClientBuilder() { - override fun build(): BaseGraphQLClient { - return BaseGraphQLClient(this.buildConfiguration()) - } - } - - /** - * Executes a GraphQL query and returns the result. - * - * @param query The GraphQL query to execute. - * @return The result of the query execution, with errors handled. - * @throws ExpediaGroupServiceException If the query execution returns errors. - */ - override fun executeAsync(query: Query): CompletableFuture { - val promise: CompletableFuture = CompletableFuture() - - apolloClient.query(query).enqueue { response -> - try { - if (response.hasErrors()) { - // Complete exceptionally if there are GraphQL errors - promise.completeExceptionally(ExpediaGroupServiceException(message = response.errors.toString())) - } else if (response.exception != null) { - // Complete exceptionally if there is a network or other exception - promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) - } else { - // Complete normally with the response data if no errors or exceptions - promise.complete(response.dataAssertNoErrors) - } - } catch (e: Exception) { - // Handle unexpected exceptions during callback execution - promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) - } - } - - return promise - } - - override fun execute(query: Query): T = - executeAsync(query).get() - - - /** - * Executes a GraphQL mutation and returns the result. - * - * @param mutation The GraphQL mutation to execute. - * @return The result of the mutation execution, with errors handled. - * @throws ExpediaGroupServiceException If the mutation execution returns errors. - */ - override fun executeAsync(mutation: Mutation): CompletableFuture { - val promise: CompletableFuture = CompletableFuture() - - apolloClient.mutation(mutation).enqueue { response -> - try { - if (response.hasErrors()) { - // Complete exceptionally if there are GraphQL errors - promise.completeExceptionally(ExpediaGroupServiceException(response.errors.toString())) - } else if (response.exception != null) { - // Complete exceptionally if there is a network or other exception - promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) - } else { - // Complete normally with the response data if no errors or exceptions - promise.complete(response.dataAssertNoErrors) - } - } catch (e: Exception) { - // Handle unexpected exceptions during callback execution - promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) - } - } - - return promise - } - - override fun execute(mutation: Mutation): T = - executeAsync(mutation).get() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt deleted file mode 100644 index 06873592..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/GraphQLExecutor.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql - -import com.apollographql.apollo.api.Mutation -import com.apollographql.apollo.api.Query -import java.util.concurrent.CompletableFuture - -interface GraphQLExecutor { - fun executeAsync(query: Query): CompletableFuture - fun executeAsync(mutation: Mutation): CompletableFuture - fun execute(query: Query): T - fun execute(mutation: Mutation): T -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt deleted file mode 100644 index 4a7cc1aa..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter - -import com.apollographql.apollo.api.Adapter -import com.apollographql.apollo.api.CustomScalarAdapters -import com.apollographql.apollo.api.json.JsonReader -import com.apollographql.apollo.api.json.JsonWriter -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -/** - * Converts the custom scalar `DateTime` to and from `java.time.OffsetDateTime`. - */ -object DateTimeAdapter : Adapter { - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): OffsetDateTime? { - val dateString = reader.nextString() ?: return null - return OffsetDateTime.parse(dateString, DateTimeFormatter.ISO_OFFSET_DATE_TIME) - } - - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: OffsetDateTime?) { - if (value != null) { - writer.value(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) - } else { - writer.nullValue() - } - } -} - - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt deleted file mode 100644 index 54a4756e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/URLAdapter.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter - -import com.apollographql.apollo.api.Adapter -import com.apollographql.apollo.api.CustomScalarAdapters -import com.apollographql.apollo.api.json.JsonReader -import com.apollographql.apollo.api.json.JsonWriter -import java.net.MalformedURLException -import java.net.URI -import java.net.URISyntaxException -import java.net.URL - -/** - * Converts the custom scalar `Url` to and from `java.net.URL`. - */ -object URLAdapter : Adapter { - - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): URL? { - val urlString = reader.nextString() ?: return null - return try { - URI(urlString).toURL() - } catch (e: URISyntaxException) { - throw IllegalStateException("Invalid URI format: $urlString", e) - } catch (e: MalformedURLException) { - throw IllegalStateException("Invalid URL format: $urlString", e) - } - } - - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: URL?) { - if (value != null) { - writer.value(value.toString()) - } else { - writer.nullValue() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt deleted file mode 100644 index 3fde3ce3..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.adapter - -import com.apollographql.apollo.api.Adapter -import com.apollographql.apollo.api.CustomScalarAdapters -import com.apollographql.apollo.api.json.JsonReader -import com.apollographql.apollo.api.json.JsonWriter -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -/** - * Converts ZoneDateTime custom scalar `Url` to and from `java.time.format.DateTimeFormatter`. - */ -object ZoneDateTimeAdapter : Adapter { - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): ZonedDateTime? { - val dateString = reader.nextString() ?: return null - return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_ZONED_DATE_TIME) - } - - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: ZonedDateTime?) { - if (value != null) { - writer.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) - } else { - writer.nullValue() - } - } -} - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt deleted file mode 100644 index 51ec7797..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/payment/PaymentClient.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.payment - -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * @constructor Creates a new instance of `PaymentClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * PaymentClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class PaymentClient(config: ClientConfiguration): - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getPaymentClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint - ), - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt deleted file mode 100644 index 49451a62..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.sandbox - -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Sandbox GraphQL API. - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * @constructor Creates a new instance of `SandboxDataManagementClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * SandboxDataManagementClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class SandboxDataManagementClient(config: ClientConfiguration) : - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getSandboxDataManagementClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint, - defaultEnvironment = ClientEnvironment.SANDBOX_PROD - ), - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt deleted file mode 100644 index 9ab8487c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/v2/lodgingconnectivity/graphql/supply/SupplyClient.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.v2.lodgingconnectivity.graphql.supply - -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Supply GraphQL API that exposes various lodging capabilities - * such as reservations, promotions, reviews, notifications, messaging, etc... - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * In addition, this client can be configured to target the sandbox environment by passing `ClientEnvironment.SANDBOX_PROD` or - * `ClientEnvironment.SANDBOX_TEST` to the `environment` configuration option. - * - * @constructor Creates a new instance of `SupplyClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * SupplyClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class SupplyClient(config: ClientConfiguration) : - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getSupplyClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint - ), - ) diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java index a90cff2f..e0119978 100644 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java @@ -1,8 +1,8 @@ package com.expediagroup.sdk.lodgingconnectivity; -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client.FileManagementClient; +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration; +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment; +import com.expediagroup.sdk.lodgingconnectivity.filemanagement.client.FileManagementClient; import java.io.File; import java.io.IOException; diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java similarity index 94% rename from examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java rename to examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java index 1ec63b62..b58ebed3 100644 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/LodgingSupplySandboxDataManagementClientUsageExample.java +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.expediagroup.sdk.lodgingconnectivity.v2; +package com.expediagroup.sdk.lodgingconnectivity; import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.*; import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.*; -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; -import com.expediagroup.sdk.v2.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration; +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment; +import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; import java.time.LocalDate; import java.util.ArrayList; diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java deleted file mode 100644 index aa7258e5..00000000 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity; - -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxCancelReservationMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxChangeReservationStayDatesMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxCreatePropertyMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxCreateReservationMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxDeletePropertyMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxDeleteReservationMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxPropertiesQuery; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxUpdatePropertyMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxUpdateReservationMutation; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.CancelReservationInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.ChangeReservationStayDatesInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.CreatePropertyInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.CreateReservationInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.DeletePropertyInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.DeleteReservationInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.UpdatePropertyInput; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.UpdateReservationInput; - -import java.time.LocalDate; -import java.util.Arrays; -import java.util.Optional; - -/** - * Example class to demonstrate different operations supported by the SandboxDataManagementClient - * Run the main method to see these operations in action: - * 1. Create a Property - * 2. Update Property Name - * 3. Create a Reservation - * 4. Update the Reservation - * 5. Update the Reservation's Stay Dates - * 6. Cancel the Reservation - * 7. Delete the Reservation - * 8. Delete the Property - **/ -public class SandboxDataManagementClientUsageExample { - - private static final SandboxDataManagementClient client = new SandboxDataManagementClient( - ClientConfiguration - .builder() - .key("KEY") - .secret("SECRET") - .build() - ); - - private static final String PROPERTY_NAME = "Lodging SDK Test Property"; - private static final String UPDATED_PROPERTY_NAME = "New Lodging SDK Test Property"; - - public static void main(String[] args) { - // Delete any old property if it has the same name used in the test run - deletePropertyIfExists(); - - // ******* Create Property ******* - CreatePropertyInput createPropertyInput = CreatePropertyInput.builder().name(Optional.of(PROPERTY_NAME)).build(); - SandboxCreatePropertyMutation.Data createPropertyResponse = client.execute(new SandboxCreatePropertyMutation(createPropertyInput)); - - - String propertyId = createPropertyResponse.createProperty.property.id; - - System.out.println("Property Created: " + propertyId); - System.out.println(createPropertyResponse); - - - // ******* Update Property Name ******* - UpdatePropertyInput updatePropertyInput = UpdatePropertyInput.builder().id(propertyId).name(Optional.of(UPDATED_PROPERTY_NAME)).build(); - SandboxUpdatePropertyMutation.Data updatePropertyResponse = client.execute(new SandboxUpdatePropertyMutation(updatePropertyInput)); - - System.out.println("Property Updated: " + propertyId); - System.out.println(updatePropertyResponse); - - - // ******* Create Reservation ******* - CreateReservationInput createReservationInput = CreateReservationInput - .builder() - .propertyId(propertyId) - .childCount(Optional.of(4)) - .adultCount(Optional.of(2)) - .build(); - - SandboxCreateReservationMutation.Data createReservationResponse = client.execute(new SandboxCreateReservationMutation(createReservationInput)); - - - String reservationId = createReservationResponse.createReservation.reservation.sandboxReservationFragment.id; - - System.out.println("Reservation Created: " + reservationId); - System.out.println(createReservationResponse); - - - // ******* Update Reservation ******* - UpdateReservationInput updateReservationInput = UpdateReservationInput - .builder() - .id(reservationId) - .childAges(Optional.of(Arrays.asList(3, 5, 7))) - .build(); - - SandboxUpdateReservationMutation.Data updateReservationResponse = client.execute(new SandboxUpdateReservationMutation(updateReservationInput)); - - System.out.println("Reservation Updated: " + reservationId); - System.out.println(updateReservationResponse); - - - // ******* Update Reservation Stay Dates ******* - ChangeReservationStayDatesInput changeStayDatesInput = ChangeReservationStayDatesInput - .builder() - .id(reservationId) - .checkInDate(LocalDate.of(2024, 6, 5)) - .checkOutDate(LocalDate.of(2024, 6, 10)) - .build(); - - SandboxChangeReservationStayDatesMutation.Data changeStayDatesResponse = client.execute(new SandboxChangeReservationStayDatesMutation(changeStayDatesInput)); - - System.out.println("Reservation Stay Dates Updated: " + reservationId); - System.out.println(changeStayDatesResponse); - - - // ******* Cancel Reservation ******* - CancelReservationInput cancelReservationInput = CancelReservationInput - .builder() - .id(reservationId) - .sendNotification(Optional.of(false)) - .build(); - - SandboxCancelReservationMutation.Data cancelReservationResponse = client.execute(new SandboxCancelReservationMutation(cancelReservationInput)); - - System.out.println("Reservation Was Canceled: " + reservationId); - System.out.println(cancelReservationResponse); - - - // ******* Delete Reservation ******* - DeleteReservationInput deleteReservationInput = DeleteReservationInput.builder().id(reservationId).build(); - SandboxDeleteReservationMutation.Data deleteReservationResponse = client.execute(new SandboxDeleteReservationMutation(deleteReservationInput)); - - System.out.println("Reservation Was Deleted: " + reservationId); - System.out.println(deleteReservationResponse); - - - // ******* Delete Property ******* - DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(propertyId).build(); - SandboxDeletePropertyMutation.Data deletePropertyResponse = client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); - - System.out.println("Property Was Deleted: " + propertyId); - System.out.println(deletePropertyResponse); - - System.exit(0); - } - - private static void deletePropertyIfExists() { - SandboxPropertiesQuery propertiesQuery = SandboxPropertiesQuery.builder().skipReservations(true).build(); - SandboxPropertiesQuery.Data propertiesResponse = client.execute(propertiesQuery); - - propertiesResponse.properties.elements.forEach(property -> { - if (property.name.equals(PROPERTY_NAME) || property.name.equals(UPDATED_PROPERTY_NAME)) { - System.out.println("Deleting existing property: ID: " + property.id + ", Name: " + property.name); - DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(property.id).build(); - client.execute(new SandboxDeletePropertyMutation(deletePropertyInput)); - } - }); - } -} diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java deleted file mode 100644 index ce093df5..00000000 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/v2/FileManagementExample.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.v2; - -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.v2.lodgingconnectivity.configuration.ClientEnvironment; -import com.expediagroup.sdk.v2.lodgingconnectivity.filemanagement.client.FileManagementClient; - -import java.io.File; -import java.io.IOException; - -public class FileManagementExample { - static FileManagementClient client = new FileManagementClient( - ClientConfiguration.builder() - .key("KEY") - .secret("SECRET") - .environment(ClientEnvironment.TEST) - .build() - ); - - public static void main(String[] args) throws IOException { - client.upload( - new File("code/src/main/resources/test.txt"), - "messageThreadId", - "6ddb1317-bf3d-4759-bcdf-d52642cfac88" - ); - } -} From 54c5a33eb2ec3d6e9b2df6e2e520a1ecd19f5b07 Mon Sep 17 00:00:00 2001 From: Mohammad Dwairi <49045447+Mohammad-Dwairi@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:07:58 +0300 Subject: [PATCH 05/15] feat: merge latest changes from main to the feature branch (#100) --- PULL_REQUEST_TEMPLATE.md | 17 + README.md | 14 +- apollo-compiler-plugin/build.gradle | 12 + .../expediagroup/sdk/apollo/PluginProvider.kt | 14 + .../sdk/apollo/plugin/StaticBuilderPlugin.kt | 132 ++++++++ ...ollo.compiler.ApolloCompilerPluginProvider | 1 + code/build.gradle | 25 +- code/src/main/graphql | 2 +- .../sdk/core/configuration/SdkMetadata.kt | 6 +- .../sdk/core/constant/LogMaskingFields.kt | 4 +- .../sdk/core/extension/NullableExtension.kt | 9 + .../graphql/common/DefaultGraphQLExecutor.kt | 157 +++++++++ .../sdk/graphql/common/GraphQLClient.kt | 33 ++ .../sdk/graphql/common/GraphQLExecutor.kt | 83 +++++ .../graphql/extension/ApolloErrorExtension.kt | 25 ++ .../model/exception/NoDataException.kt | 36 ++ .../sdk/graphql/model/paging/PageInfo.kt | 34 ++ .../graphql/model/paging/PaginatedStream.kt | 77 +++++ .../sdk/graphql/model/response/Error.kt | 29 ++ .../model/response/PaginatedResponse.kt | 38 +++ .../sdk/graphql/model/response/RawResponse.kt | 36 ++ .../sdk/graphql/model/response/Response.kt | 45 +++ .../configuration/ApiEndpoint.kt | 29 ++ .../configuration/ClientConfiguration.kt | 30 +- .../configuration/ClientEnvironment.kt | 20 +- .../configuration/EndpointProvider.kt | 56 ++-- .../FileManagementApiEndpointProvider.kt | 25 ++ .../PaymentApiEndpointProvider.kt | 41 +++ .../SandboxApiEndpointProvider.kt | 41 +++ .../SupplyApiEndpointProvider.kt | 41 +++ .../client/FileManagementClient.kt | 7 +- .../models/FileUploadRequest.kt | 5 +- .../graphql/BaseGraphQLClient.kt | 121 ------- .../graphql/adapter/DateTimeAdapter.kt | 42 --- .../graphql/adapter/URLAdapter.kt | 19 +- .../graphql/adapter/ZoneDateTimeAdapter.kt | 42 --- .../graphql/payment/PaymentClient.kt | 51 --- .../sandbox/SandboxDataManagementClient.kt | 53 --- .../graphql/supply/SupplyClient.kt | 55 ---- .../payment/PaymentClient.kt | 60 ++++ .../GetPaymentInstrumentOperation.kt | 64 ++++ .../sandbox/SandboxDataManagementClient.kt | 307 ++++++++++++++++++ .../CreateSandboxPropertyOperation.kt | 60 ++++ .../DeleteSandboxPropertyOperation.kt | 60 ++++ .../GetSandboxPropertiesOperation.kt | 85 +++++ .../operation/GetSandboxPropertyOperation.kt | 58 ++++ .../UpdateSandboxPropertyOperation.kt | 62 ++++ .../paginator/SandboxPropertiesPaginator.kt | 124 +++++++ .../CancelSandboxReservationOperation.kt | 59 ++++ ...ngeSandboxReservationStayDatesOperation.kt | 62 ++++ .../CreateSandboxReservationOperation.kt | 57 ++++ .../DeleteSandboxReservationOperation.kt | 60 ++++ .../DeleteSandboxReservationsOperation.kt | 60 ++++ .../GetSandboxReservationOperation.kt | 56 ++++ .../GetSandboxReservationsOperation.kt | 86 +++++ .../UpdateSandboxReservationOperation.kt | 61 ++++ .../paginator/SandboxReservationsPaginator.kt | 122 +++++++ .../supply/reservation/ReservationClient.kt | 278 ++++++++++++++++ .../reservation/constant/Constant.kt} | 13 +- .../operation/CancelReservationOperation.kt | 74 +++++ ...ancelReservationReconciliationOperation.kt | 74 +++++ .../CancelVrboReservationOperation.kt | 74 +++++ ...hangeReservationReconciliationOperation.kt | 74 +++++ ...ConfirmReservationNotificationOperation.kt | 74 +++++ .../operation/GetReservationsOperation.kt | 107 ++++++ .../operation/RefundReservationOperation.kt | 74 +++++ .../paginator/ReservationsPaginator.kt | 149 +++++++++ .../reservation/stream/ReservationsStream.kt | 55 ++++ code/tasks-gradle/apollo.gradle | 60 ++-- docs/payment-client.md | 2 +- ...supply-client.md => reservation-client.md} | 43 ++- docs/sandbox-data-management-client.md | 33 +- examples/build.gradle | 1 + ...ndboxDataManagementClientUsageExample.java | 134 +++++--- gradle.properties | 2 +- gradlew.bat | 184 +++++------ settings.gradle | 1 + 77 files changed, 3861 insertions(+), 655 deletions(-) create mode 100644 PULL_REQUEST_TEMPLATE.md create mode 100644 apollo-compiler-plugin/build.gradle create mode 100644 apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/PluginProvider.kt create mode 100644 apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/plugin/StaticBuilderPlugin.kt create mode 100644 apollo-compiler-plugin/src/main/resources/META-INF/services/com.apollographql.apollo.compiler.ApolloCompilerPluginProvider create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/exception/NoDataException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PageInfo.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PaginatedStream.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/PaginatedResponse.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/RawResponse.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Response.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ApiEndpoint.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/FileManagementApiEndpointProvider.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/PaymentApiEndpointProvider.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SandboxApiEndpointProvider.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SupplyApiEndpointProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/operation/GetPaymentInstrumentOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/CreateSandboxPropertyOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/DeleteSandboxPropertyOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertiesOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertyOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/UpdateSandboxPropertyOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/paginator/SandboxPropertiesPaginator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CancelSandboxReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/ChangeSandboxReservationStayDatesOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CreateSandboxReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationsOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationsOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/UpdateSandboxReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/paginator/SandboxReservationsPaginator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt rename code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/{graphql/GraphQLExecutor.kt => supply/reservation/constant/Constant.kt} (54%) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationReconciliationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelVrboReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ChangeReservationReconciliationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ConfirmReservationNotificationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/GetReservationsOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/RefundReservationOperation.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/paginator/ReservationsPaginator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/stream/ReservationsStream.kt rename docs/{supply-client.md => reservation-client.md} (80%) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..130ea690 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +# Situation + + +# Task + + +# Action + + +# Testing + + +# Results + + +# Notes + \ No newline at end of file diff --git a/README.md b/README.md index d2dbb699..7e593e23 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Make sure you have **Java 8** or higher. ```groovy // gradle.build dependencies { - implementation 'com.expediagroup:lodging-connectivity-sdk:1.0.2-SNAPSHOT' + implementation 'com.expediagroup:lodging-connectivity-sdk:1.0.6-SNAPSHOT' } ``` @@ -29,7 +29,7 @@ dependencies { com.expediagroup lodging-connectivity-sdk - 1.0.2-SNAPSHOT + 1.0.6-SNAPSHOT ``` @@ -61,9 +61,9 @@ dependencies { > > ``` ## Quick Start -Once you have the SDK dependency installed, you can start using its capabilities. The SDK contains three different clients, each linked to a separate endpoint +Once you have the SDK dependency installed, you can start using its capabilities. The SDK contains three different clients, each used to interact with a specific capability in Lodging Connectivity APIs. -1. Supply Client +1. Reservation Client 2. Payment Client 3. Sandbox Data Management Client @@ -82,17 +82,17 @@ Follow these three simple steps to start using any client in the SDK: 2. Initialize a client. ```java - SupplyClient supplyClient = new SupplyClient(config); // Taking SupplyClient as an example + ReservationClient reservationClient = new ReservationClient(config); // Taking ReservationClient as an example ``` 3. Execute operations ```java - supplyClient.execute(/* GraphQL Operation */); + reservationClient.execute(/* GraphQL Operation */); ``` ## Documentation The list below lists detailed documentation files for some components of the SDK. Whether you're looking to configure the SDK, explore the pre-built GraphQL operations, or learn how to use specific clients, the following resources will guide you through all the necessary steps. -1. [Supply Client Documentation](docs/supply-client.md) +1. [Reservation Client Documentation](docs/reservation-client.md) 2. [Payment Client Documentation](docs/payment-client.md) 3. [Sandbox Data Management Client Documentation](docs/sandbox-data-management-client.md) 4. [Configuration](docs/configuration.md) diff --git a/apollo-compiler-plugin/build.gradle b/apollo-compiler-plugin/build.gradle new file mode 100644 index 00000000..28f248dc --- /dev/null +++ b/apollo-compiler-plugin/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '2.0.21' +} + +kotlin { + jvmToolchain(8) +} + +dependencies { + // Add apollo-compiler as a dependency + implementation("com.apollographql.apollo:apollo-compiler:4.1.0") +} diff --git a/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/PluginProvider.kt b/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/PluginProvider.kt new file mode 100644 index 00000000..848b9efd --- /dev/null +++ b/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/PluginProvider.kt @@ -0,0 +1,14 @@ +package com.expediagroup.sdk.apollo + +import com.apollographql.apollo.annotations.ApolloExperimental +import com.apollographql.apollo.compiler.ApolloCompilerPlugin +import com.apollographql.apollo.compiler.ApolloCompilerPluginEnvironment +import com.apollographql.apollo.compiler.ApolloCompilerPluginProvider +import com.expediagroup.sdk.apollo.plugin.StaticBuilderPlugin + +@OptIn(ApolloExperimental::class) +class PluginProvider : ApolloCompilerPluginProvider { + override fun create(environment: ApolloCompilerPluginEnvironment): ApolloCompilerPlugin { + return StaticBuilderPlugin() + } +} diff --git a/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/plugin/StaticBuilderPlugin.kt b/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/plugin/StaticBuilderPlugin.kt new file mode 100644 index 00000000..ab3ccf9a --- /dev/null +++ b/apollo-compiler-plugin/src/main/kotlin/com/expediagroup/sdk/apollo/plugin/StaticBuilderPlugin.kt @@ -0,0 +1,132 @@ +package com.expediagroup.sdk.apollo.plugin + +import com.apollographql.apollo.compiler.ApolloCompilerPlugin +import com.apollographql.apollo.compiler.Transform +import com.apollographql.apollo.compiler.codegen.kotlin.KotlinOutput +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec + +/** + * Custom Apollo Compiler Plugin to enhance generated Kotlin code by adding a static builder method when applicable. + */ +class StaticBuilderPlugin : ApolloCompilerPlugin { + override fun kotlinOutputTransform(): Transform { + return object : Transform { + override fun transform(input: KotlinOutput): KotlinOutput { + return KotlinOutput( + fileSpecs = input.fileSpecs.map { kotlinFile -> + FileSpec.builder(kotlinFile.packageName, kotlinFile.name) + .apply { + kotlinFile.members.forEach { member -> + when (member) { + is TypeSpec -> { + addType(processTypeSpec(member)) + } + + is FunSpec -> { + addFunction(member) + } + + is PropertySpec -> { + addProperty(member) + } + } + } + } + .build() + }, + codegenMetadata = input.codegenMetadata + ) + } + } + } + + /** + * Process the given TypeSpec to handle the presence of a nested "Builder" class. + * + * @param typeSpec the TypeSpec to process + * @return the processed TypeSpec with necessary modifications for a "Builder" class if applicable + */ + private fun processTypeSpec(typeSpec: TypeSpec): TypeSpec { + if (typeSpec.kind != TypeSpec.Kind.CLASS) { + return typeSpec + } + + val hasBuilderClass = typeSpec.typeSpecs.any { it.name == "Builder" } + + if (!hasBuilderClass) { + return typeSpec + } + + val newTypeSpecBuilder = typeSpec.toBuilder() + + var hasCompanionObject = false + val newNestedTypes = mutableListOf() + + typeSpec.typeSpecs.forEach { + if (it.kind == TypeSpec.Kind.OBJECT && (it.name == null || it.name == "Companion")) { + hasCompanionObject = true + newNestedTypes.add(processCompanionObject(it, typeSpec.name ?: "")) + } else { + newNestedTypes.add(it) + } + } + + if (!hasCompanionObject) { + newNestedTypes.add(createCompanionObjectWithBuilder(typeSpec.name ?: "")) + } + + // Clear existing nested types and add the new ones + newTypeSpecBuilder.typeSpecs.clear() + newTypeSpecBuilder.addTypes(newNestedTypes) + + return newTypeSpecBuilder.build() + } + + /** + * Process the companion object to check for the presence of a builder method and add one if it doesn't exist. + * + * @param companionObject the companion object of type TypeSpec + * @param enclosingClassName the name of the enclosing class as a String + * @return the modified TypeSpec with the builder method added to the companion object if needed + */ + private fun processCompanionObject(companionObject: TypeSpec, enclosingClassName: String): TypeSpec { + val hasBuilderMethod = companionObject.funSpecs.any { it.name == "builder" } + + if (hasBuilderMethod) { + return companionObject + } + + return companionObject + .toBuilder() + .addFunction(constructBuilderStaticMethod(enclosingClassName)) + .build() + } + + /** + * Helper function to create a companion object with a builder method. + * + * @param enclosingClassName the name of the enclosing class as a String + * @return the TypeSpec representing the companion object with the builder method + */ + private fun createCompanionObjectWithBuilder(enclosingClassName: String) = TypeSpec + .companionObjectBuilder() + .addFunction(constructBuilderStaticMethod(enclosingClassName)) + .build() + + /** + * Constructs a JvmStatic annotated function spec for a builder method within the specified enclosing class name. + * + * @param enclosingClassName the name of the enclosing class as a String + */ + private fun constructBuilderStaticMethod(enclosingClassName: String) = FunSpec + .builder("builder") + .returns(ClassName("", enclosingClassName).nestedClass("Builder")) + .addAnnotation(AnnotationSpec.builder(JvmStatic::class).build()) + .addStatement("return %T()", ClassName("", enclosingClassName).nestedClass("Builder")) + .build() +} diff --git a/apollo-compiler-plugin/src/main/resources/META-INF/services/com.apollographql.apollo.compiler.ApolloCompilerPluginProvider b/apollo-compiler-plugin/src/main/resources/META-INF/services/com.apollographql.apollo.compiler.ApolloCompilerPluginProvider new file mode 100644 index 00000000..6c5d2aec --- /dev/null +++ b/apollo-compiler-plugin/src/main/resources/META-INF/services/com.apollographql.apollo.compiler.ApolloCompilerPluginProvider @@ -0,0 +1 @@ +com.expediagroup.sdk.apollo.PluginProvider diff --git a/code/build.gradle b/code/build.gradle index d4df09a1..b25eb092 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -1,7 +1,7 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.0.20' + id 'org.jetbrains.kotlin.jvm' version '2.0.21' id 'org.jetbrains.dokka' version '1.9.20' - id 'com.apollographql.apollo' version '4.0.0' + id 'com.apollographql.apollo' version '4.1.0' /* Publishing Plugins */ id 'maven-publish' @@ -12,15 +12,6 @@ kotlin { jvmToolchain(8) } -sourceSets { - main { - java { - /** Include apollo generated sources in the final JAR **/ - srcDirs += 'build/generated/source/apollo' - } - } -} - jar { manifest { attributes( @@ -33,13 +24,13 @@ jar { dependencies { /* Kotlin */ - implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.20' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.21' /* Dokka */ dokkaHtmlPlugin 'org.jetbrains.dokka:versioning-plugin:1.9.20' /* Apollo Kotlin */ - api 'com.apollographql.apollo:apollo-runtime:4.0.0' + api 'com.apollographql.apollo:apollo-runtime:4.1.0' implementation 'com.apollographql.adapters:apollo-adapters-core:0.0.4' /* Apollo Java */ @@ -47,13 +38,7 @@ dependencies { /* EG SDK Core */ implementation("org.apache.tika:tika-core:2.9.2") - implementation("com.google.api-client:google-api-client:2.7.0") { - exclude group: 'io.grpc', module: 'grpc-api' - exclude group: 'io.grpc', module: 'grpc-context' - - exclude group: 'io.opencensus', module: 'opencensus-api' - exclude group: 'io.opencensus', module: 'opencensus-contrib-http-util' - } + implementation("com.google.api-client:google-api-client:2.7.0") implementation("com.ebay.ejmask:ejmask-api:1.0.3") implementation("com.ebay.ejmask:ejmask-extensions:1.0.3") implementation 'org.slf4j:slf4j-simple:2.0.13' diff --git a/code/src/main/graphql b/code/src/main/graphql index 1373b20c..e330f72e 160000 --- a/code/src/main/graphql +++ b/code/src/main/graphql @@ -1 +1 @@ -Subproject commit 1373b20c3c0424cb5cfb82735eaf9d925eb8498c +Subproject commit e330f72e7b2e656f6223cfe7bc578a0733bf2689 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt index 04841748..3e4669df 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt @@ -3,9 +3,9 @@ package com.expediagroup.sdk.core.configuration import java.util.jar.Manifest internal object SdkMetadata { - private lateinit var artifactId: String - private lateinit var version: String - private lateinit var userAgentPrefix: String + private var artifactId: String + private var version: String + private var userAgentPrefix: String fun getArtifactId(): String = artifactId fun getVersion(): String = version diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt index 57ab1552..9c7e35d1 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt @@ -31,6 +31,8 @@ internal data object LogMaskingFields { "account_number", "card_avs_response", "card_cvv_response", - "card_cvv2_response" + "card_cvv2_response", + "verificationNumber", + "vatNumber" ) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt new file mode 100644 index 00000000..855584e0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt @@ -0,0 +1,9 @@ +package com.expediagroup.sdk.core.extension + +inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { + return this ?: throw exceptionProvider() +} + +fun Boolean?.orFalseIfNull(): Boolean = this ?: false + +fun String?.orNullIfBlank(): String? = this?.takeUnless { it.isBlank() } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt new file mode 100644 index 00000000..773cbf59 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.common + +import com.apollographql.apollo.api.ApolloResponse +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Operation +import com.apollographql.apollo.api.Query +import com.apollographql.java.client.ApolloClient +import com.expediagroup.sdk.core.client.ApiClientApolloHttpEngine +import com.expediagroup.sdk.core.client.util.createApiClient +import com.expediagroup.sdk.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.extension.toSDKError +import com.expediagroup.sdk.graphql.model.exception.NoDataException +import com.expediagroup.sdk.graphql.model.response.RawResponse +import java.util.concurrent.CompletableFuture + +/** + * Default implementation of [GraphQLExecutor], responsible for executing GraphQL queries and mutations + * using Apollo Kotlin with a custom HTTP client. + * + * This executor leverages the Apollo Client to perform requests and processes responses by capturing + * the entire data structure and any errors in a [RawResponse], which can then be further processed or + * filtered by higher-level components in the SDK. + * + * By default - this implementation is used internally in all higher-level clients that extend [GraphQLClient] abstract class + * + * @param configuration Configuration details required to set up the custom client and Apollo Client. + */ +internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : GraphQLExecutor() { + + private val engine = ApiClientApolloHttpEngine(createApiClient(configuration = configuration)) + + /** + * The Apollo Client used to execute GraphQL requests, configured with a custom HTTP client. + */ + override val apolloClient: ApolloClient = ApolloClient.Builder() + .serverUrl(configuration.getEndpoint()) + .httpEngine(engine) + .build() + + + /** + * Asynchronously executes a GraphQL query and returns a [CompletableFuture] containing the complete + * data and any errors wrapped in [RawResponse]. + * + * @param query The GraphQL query to be executed. + * @return A [CompletableFuture] with the full data structure and any errors from the server. + * @throws ExpediaGroupServiceException If an exception occurs during query execution. + * @throws NoDataException If the query completes without data but includes errors. + */ + override fun executeAsync(query: Query): CompletableFuture> { + return CompletableFuture>().also { + apolloClient.query(query).enqueue { response -> processOperationResponse(response, it) } + } + } + + /** + * Executes a GraphQL query and returns a [RawResponse] containing the complete data and any errors. + * + * @param query The GraphQL query to be executed. + * @return A [RawResponse] with the full data structure and any errors from the server. + * @throws ExpediaGroupServiceException If an exception occurs during query execution. + * @throws NoDataException If the query completes without data but includes errors. + */ + override fun execute(query: Query): RawResponse = executeAsync(query).get() + + /** + * Asynchronously executes a GraphQL mutation and returns a [CompletableFuture] containing the complete + * data and any errors wrapped in [RawResponse]. + * + * @param mutation The GraphQL mutation to be executed. + * @return A [CompletableFuture] with the full data structure and any errors from the server. + * @throws ExpediaGroupServiceException If an exception occurs during mutation execution. + * @throws NoDataException If the mutation completes without data but includes errors. + */ + override fun executeAsync(mutation: Mutation): CompletableFuture> { + return CompletableFuture>().also { + apolloClient.mutation(mutation).enqueue { response -> processOperationResponse(response, it) } + } + } + + /** + * Executes a GraphQL mutation and returns a [RawResponse] containing the complete data and any errors. + * + * @param mutation The GraphQL mutation to be executed. + * @return A [RawResponse] with the full data structure and any errors from the server. + * @throws ExpediaGroupServiceException If an exception occurs during mutation execution. + * @throws NoDataException If the mutation completes without data but includes errors. + */ + override fun execute(mutation: Mutation): RawResponse = executeAsync(mutation).get() + + + /** + * Handles the response from a GraphQL operation, determining whether to complete the provided CompletableFuture + * with either success or an exception based on the response data and errors. + * + * @param response The ApolloResponse containing the data and errors from the GraphQL operation. + * @param future A CompletableFuture that will be completed based on the response handling logic. + */ + private fun processOperationResponse( + response: ApolloResponse, + future: CompletableFuture> + ) { + try { + when { + response.exception != null -> { + future.completeExceptionally( + ExpediaGroupServiceException( + message = response.exception?.message, + cause = response.exception + ) + ) + } + + response.data != null && response.hasErrors() -> { + future.completeExceptionally( + NoDataException( + message = "No data received from the server", + errors = response.errors!!.map { it.toSDKError() }) + ) + } + + else -> { + future.complete( + RawResponse( + data = response.data!!, + errors = response.errors?.map { it.toSDKError() } + ) + ) + } + } + } catch (e: Exception) { + future.completeExceptionally( + ExpediaGroupServiceException( + message = e.message, + cause = e + ) + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt new file mode 100644 index 00000000..f44c39f9 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.common + +/** + * Abstract base class for GraphQL clients that defines the core structure for executing GraphQL operations. + * Classes extending `GraphQLClient` are expected to provide an implementation of the [GraphQLExecutor]. + * + * This design allows subclasses to define custom logic for executing GraphQL queries and mutations + * while relying on the `graphQLExecutor` to perform the actual request handling. + */ +abstract class GraphQLClient { + /** + * The executor responsible for handling GraphQL operations. + * Subclasses must provide a concrete implementation of this executor to define + * how queries and mutations are processed. + */ + protected abstract val graphQLExecutor: GraphQLExecutor +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt new file mode 100644 index 00000000..bdf31ed5 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.common + +import com.apollographql.java.client.ApolloClient +import com.apollographql.apollo.api.Mutation +import com.apollographql.apollo.api.Query +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.model.response.RawResponse +import java.util.concurrent.CompletableFuture + +/** + * Abstract base class for executing GraphQL operations, providing a structure for executing queries and mutations + * and returning the full response data along with any errors. + * + * This class is designed to handle the execution of GraphQL operations and return a [RawResponse] containing + * the complete, unprocessed data and error details. Subclasses should implement specific behaviors for how + * requests are sent and processed. + */ +abstract class GraphQLExecutor { + + /** + * The Apollo Client instance used to perform GraphQL requests. + * Subclasses must initialize this property with a configured [ApolloClient] instance. + */ + protected abstract val apolloClient: ApolloClient + + /** + * Executes a GraphQL query and returns the complete raw response. + * + * @param query The GraphQL query to be executed. + * @return A [RawResponse] containing the full data and any errors from the query response. + * @throws ExpediaGroupServiceException If an exception occurs during the execution of the query. + * @throws NoDataException If the query completes without data but includes errors. + */ + abstract fun execute(query: Query): RawResponse + + /** + * Executes a GraphQL mutation and returns the complete raw response. + * + * @param mutation The GraphQL mutation to be executed. + * @return A [RawResponse] containing the full data and any errors from the mutation response. + * @throws ExpediaGroupServiceException If an exception occurs during the execution of the mutation. + * @throws NoDataException If the mutation completes without data but includes errors. + */ + abstract fun execute(mutation: Mutation): RawResponse + + /** + * Asynchronously executes a GraphQL query and returns the complete raw response in a [CompletableFuture]. + * + * @param query The GraphQL query to be executed. + * @return A [CompletableFuture] containing the full data and any errors from the query response wrapped in [RawResponse]. + * @throws ExpediaGroupServiceException If an exception occurs during the execution of the query. + * @throws NoDataException If the query completes without data but includes errors. + */ + abstract fun executeAsync(query: Query): CompletableFuture> + + /** + * Asynchronously executes a GraphQL mutation and returns the complete raw response in a [CompletableFuture]. + * + * @param query The GraphQL mutation to be executed. + * @return A [CompletableFuture] containing the full data and any errors from the mutation response wrapped in [RawResponse]. + * @throws ExpediaGroupServiceException If an exception occurs during the execution of the mutation. + * @throws NoDataException If the mutation completes without data but includes errors. + */ + abstract fun executeAsync(mutation: Mutation): CompletableFuture> +} + + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt new file mode 100644 index 00000000..2963f44d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.extension + +import com.apollographql.apollo.api.Error + +fun Error.toSDKError() = + com.expediagroup.sdk.graphql.model.response.Error( + message = this.message, + path = this.path?.map { it.toString() } + ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/exception/NoDataException.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/exception/NoDataException.kt new file mode 100644 index 00000000..63cae2ef --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/exception/NoDataException.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.exception + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.model.response.Error + +/** + * Exception thrown when a GraphQL response completes without data but includes error details. + * + * Typically used to indicate that the server processed the request but encountered issues, + * resulting in an empty data payload accompanied by one or more errors. + * + * @param message An optional message providing context about the exception. + * @param cause The underlying cause of the exception, if available. + * @param errors A list of [Error] objects describing issues from the GraphQL response, useful for precise error handling. + */ +class NoDataException( + message: String? = null, + cause: Throwable? = null, + val errors: List, +) : ExpediaGroupServiceException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PageInfo.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PageInfo.kt new file mode 100644 index 00000000..2e64ba77 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PageInfo.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.paging + +/** + * Represents pagination information for a paginated response. + * + * @param pageSize The number of items per page. + * @param hasNext Indicates if there is a next page available. + * @param cursor The current cursor position, if applicable. + * @param nextPageCursor The cursor for the next page, used to fetch the following set of results. + * @param totalCount The total count of items across all pages, if available. + */ +data class PageInfo( + val pageSize: Int, + val hasNext: Boolean, + val cursor: String? = null, + val nextPageCursor: String? = null, + val totalCount: Int? = null +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PaginatedStream.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PaginatedStream.kt new file mode 100644 index 00000000..9a9510bd --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/paging/PaginatedStream.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.paging + +import java.util.stream.Stream +import kotlin.streams.asStream + +/** + * A utility for sequentially retrieving items from a paginated data source as a continuous stream. + * Items are fetched one at a time, with new pages loaded as needed. + * + * Subclasses implement the `nextItem` method to define how items are retrieved from paginated sources. + * + * @param T The type of items contained in the paginated stream. + */ +abstract class PaginatedStream { + + // Holds the current page of items fetched from the data source. + private var currentPage: ArrayDeque = ArrayDeque() + + /** + * Returns a [Stream] of items, sequentially fetching new items as needed from `nextItem`. + * + * @return A [Stream] that lazily retrieves items. + */ + fun stream(): Stream = generateSequence { nextItem() }.asStream() + + /** + * Retrieves the next item in the paginated sequence. Subclasses must provide an implementation + * to specify how items are fetched. + * + * @return The next item in the stream, or `null` if no more items are available. + */ + protected abstract fun nextItem(): T? + + /** + * Fetches the next page of items and updates the current page. + * + * @param pageSupplier A function that supplies the next page of items as a list. + */ + protected fun fetchNextPage(pageSupplier: () -> List) { + currentPage = ArrayDeque(pageSupplier()) + } + + /** + * Retrieves and removes the next item from the current page, or returns `null` if the page is empty. + * + * @return The next item from the current page, or `null` if there are no more items. + */ + protected fun pollCurrentPage(): T? = try { + currentPage.removeFirst() + } catch (e: NoSuchElementException) { + null + } + + /** + * Checks if the current page is empty. + * + * @return `true` if the current page has no more items, otherwise `false`. + */ + protected fun isCurrentPageEmpty(): Boolean = currentPage.isEmpty() +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt new file mode 100644 index 00000000..208519c0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.response + +/** + * Represents an error returned from a GraphQL operation. + * + * @param message A message detailing the nature of the error. + * @param path The path in the GraphQL query where the error occurred, represented as a list of field names. + * This may be `null` if the error is not tied to a specific query path. + */ +data class Error( + val message: String, + val path: List?, +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/PaginatedResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/PaginatedResponse.kt new file mode 100644 index 00000000..f54ce8f0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/PaginatedResponse.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.response + +import com.apollographql.apollo.api.Operation +import com.expediagroup.sdk.graphql.model.paging.PageInfo + +/** + * Represents a paginated response, extending a [Response] with pagination metadata. + * + * This interface is intended for paginated responses where data spans multiple pages, providing access to + * a specific data fragment of interest, the full raw response, and pagination details. + * + * @param T The type of the specific data fragment that the user is interested in, allowing for direct access to that data. + * @param R The type of the raw response data, constrained to types extending [Operation.Data], typically containing + * the complete unprocessed data and error information from the GraphQL operation. + */ +interface PaginatedResponse : Response { + + /** + * Metadata about the pagination state, including page size, cursor position, and whether additional pages are available. + */ + val pageInfo: PageInfo +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/RawResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/RawResponse.kt new file mode 100644 index 00000000..bd4cd363 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/RawResponse.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.response + +import com.apollographql.apollo.api.Operation + +/** + * Represents the full, unprocessed response from a GraphQL operation, including the entire data structure + * and any associated errors. + * + * This class serves as a wrapper for the raw response, providing access to both the complete data and error details. + * Higher-level interfaces, such as [Response], can then extract and simplify access to specific data fragments + * relevant to the user, while `RawResponse` retains the entire response as received. + * + * @param T The type of the full data structure returned by the GraphQL operation, constrained to types extending [Operation.Data]. + * @param data The complete data returned by the GraphQL operation, representing all available fields as per the operation. + * @param errors A list of errors associated with the response, or `null` if no errors were encountered. + */ +open class RawResponse( + val data: T, + val errors: List? +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Response.kt new file mode 100644 index 00000000..cb1bd83e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Response.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.graphql.model.response + +import com.apollographql.apollo.api.Operation + +/** + * Represents a response from a data source, containing both the specific data fragment of interest and the underlying raw response. + * + * This interface allows users to directly access a targeted portion of the response data, simplifying data access + * by focusing on a specific fragment within a potentially nested response structure. + * + * @param T The type of the specific data fragment that the user is interested in, allowing for direct access to that data. + * @param R The type of the raw response data, constrained to types extending [Operation.Data], typically containing + * the complete unprocessed data and error information from the GraphQL operation. + */ +interface Response { + + /** + * The specific data fragment of interest to the user, extracted from the full response. + * This fragment simplifies access to nested data by focusing on the relevant portion directly. + */ + val data: T + + /** + * The raw response received from the data source, containing the complete unprocessed data and any errors. + * This provides access to the full response structure when needed. + */ + val rawResponse: RawResponse +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ApiEndpoint.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ApiEndpoint.kt new file mode 100644 index 00000000..c2162451 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ApiEndpoint.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.configuration + +/** + * Represents the endpoints required for API communication, including both the main API endpoint + * and the authentication endpoint. + * + * @param endpoint The primary API endpoint used for general requests. + * @param authEndpoint The endpoint used specifically for authentication requests. + */ +data class ApiEndpoint( + val endpoint: String, + val authEndpoint: String +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt index d76b10be..9795e825 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.lodgingconnectivity.configuration import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException @@ -147,13 +163,7 @@ data class ClientConfiguration( fun builder(): Builder = Builder() } - internal fun toFullClientConfiguration( - endpointProvider: (ClientEnvironment) -> String, - authEndpointProvider: (ClientEnvironment) -> String, - defaultEnvironment: ClientEnvironment = ClientEnvironment.PROD - ): FullClientConfiguration { - val environment = this.environment ?: defaultEnvironment - + internal fun toFullClientConfiguration(apiEndpoint: ApiEndpoint): FullClientConfiguration { return object : FullClientConfiguration { override fun getKey(): String = key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") @@ -161,11 +171,9 @@ data class ClientConfiguration( override fun getSecret(): String = secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") - override fun getEndpoint(): String = - endpointProvider(environment) + override fun getEndpoint(): String = apiEndpoint.endpoint - override fun getAuthEndpoint(): String = - authEndpointProvider(environment) + override fun getAuthEndpoint(): String = apiEndpoint.authEndpoint override fun getMaskedLoggingHeaders(): Set = maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt index 1e4ae899..e05f4aa3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientEnvironment.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.lodgingconnectivity.configuration /** @@ -24,14 +40,14 @@ enum class ClientEnvironment { /** * Represents the sandbox version of the production environment. * - * This environment is only available for `SupplyClient` + * This environment is only available for `ReservationClient` */ SANDBOX_PROD, /** * Represents the sandbox version of the EG internal test/lab environment. * - * This environment is only available for `SupplyClient` + * This environment is only available for `ReservationClient` */ SANDBOX_TEST } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt index 4a8367bb..0ba4ecfd 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/EndpointProvider.kt @@ -1,20 +1,38 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.expediagroup.sdk.lodgingconnectivity.configuration import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -enum class SupplyClientEndpoint(val url: String) { +enum class SupplyApiEndpoint(val url: String) { PROD("https://api.expediagroup.com/supply/lodging/graphql"), TEST("https://test-api.expediagroup.com/supply/lodging/graphql"), SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging/graphql"), SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging/graphql") } -enum class PaymentClientEndpoint(val url: String) { +enum class PaymentApiEndpoint(val url: String) { PROD("https://api.expediagroup.com/supply/payments/graphql"), - TEST("https://test-api.expediagroup.com/supply/payments/graphql") + TEST("https://test-api.expediagroup.com/supply/payments/graphql"), + SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/payments/graphql"), + SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/payments/graphql") } -enum class SandboxDataManagementClientEndpoint(val url: String) { +enum class SandboxApiEndpoint(val url: String) { SANDBOX_PROD("https://api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql"), SANDBOX_TEST("https://test-api.sandbox.expediagroup.com/supply/lodging-sandbox/graphql") } @@ -35,46 +53,46 @@ enum class AuthEndpoint(val url: String) { * An internal utility object for providing API endpoints based on the environment. * * This class contains methods to retrieve the correct endpoint for different clients - * (e.g., Supply, Payment, Sandbox, File Management, and Authentication) based on the + * (e.g., Supply, Payment, Sandbox, and Authentication) based on the * provided `ClientEnvironment`. * * If an unsupported environment is passed, an `IllegalArgumentException` is thrown. */ -object EndpointProvider { - fun getSupplyClientEndpoint(environment: ClientEnvironment): String { +internal object EndpointProvider { + fun getSupplyApiEndpoint(environment: ClientEnvironment): String { return try { - SupplyClientEndpoint.valueOf(environment.name).url + SupplyApiEndpoint.valueOf(environment.name).url } catch (e: IllegalArgumentException) { throw ExpediaGroupConfigurationException( """ - Unsupported environment [$environment] for SupplyClient. - Supported environments are [${SupplyClientEndpoint.entries.joinToString(", ") }] + Unsupported environment [$environment] for Supply API. + Supported environments are [${SupplyApiEndpoint.entries.joinToString(", ") }] """ ) } } - fun getPaymentClientEndpoint(environment: ClientEnvironment): String { + fun getPaymentApiEndpoint(environment: ClientEnvironment): String { return try { - PaymentClientEndpoint.valueOf(environment.name).url + PaymentApiEndpoint.valueOf(environment.name).url } catch (e: IllegalArgumentException) { throw ExpediaGroupConfigurationException( """ - Unsupported environment [$environment] for PaymentClient. - Supported environments are [${PaymentClientEndpoint.entries.joinToString(", ") }] + Unsupported environment [$environment] for Payment API. + Supported environments are [${PaymentApiEndpoint.entries.joinToString(", ") }] """ ) } } - fun getSandboxDataManagementClientEndpoint(environment: ClientEnvironment): String { + fun getSandboxApiEndpoint(environment: ClientEnvironment): String { return try { - SandboxDataManagementClientEndpoint.valueOf(environment.name).url + SandboxApiEndpoint.valueOf(environment.name).url } catch (e: IllegalArgumentException) { throw ExpediaGroupConfigurationException( """ - Unsupported environment [$environment] for SandboxDataManagementClient. - Supported environments are [${SandboxDataManagementClientEndpoint.entries.joinToString(", ") }] + Unsupported environment [$environment] for Sandbox API. + Supported environments are [${SandboxApiEndpoint.entries.joinToString(", ") }] """ ) } @@ -94,7 +112,7 @@ object EndpointProvider { } catch (e: IllegalArgumentException) { throw ExpediaGroupConfigurationException( """ - Unsupported environment [$environment] for Authentication. + Unsupported environment [$environment] for Authentication API. Supported environments are [${AuthEndpoint.entries.joinToString(", ") }] """ ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/FileManagementApiEndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/FileManagementApiEndpointProvider.kt new file mode 100644 index 00000000..74e82a0b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/FileManagementApiEndpointProvider.kt @@ -0,0 +1,25 @@ +package com.expediagroup.sdk.lodgingconnectivity.configuration + +/** + * Provides needed endpoints for EG lodging connectivity File Management API, configured based on the specified client environment. + */ +class FileManagementApiEndpointProvider private constructor() { + companion object { + /** + * Returns an [ApiEndpoint] configured for the specified environment. + * + * This method selects the appropriate API and authentication endpoints based on the given + * [ClientEnvironment] to ensure compatibility with different environments (e.g., PROD, TEST). + * + * @param environment The [ClientEnvironment] specifying the target environment (e.g., PROD, TEST). + * @return An [ApiEndpoint] containing the appropriate endpoints for the specified environment. + */ + @JvmStatic + fun forEnvironment(environment: ClientEnvironment): ApiEndpoint { + return ApiEndpoint( + endpoint = EndpointProvider.getFileManagementClientEndpoint(environment), + authEndpoint = EndpointProvider.getAuthEndpoint(environment) + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/PaymentApiEndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/PaymentApiEndpointProvider.kt new file mode 100644 index 00000000..aa80ea1b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/PaymentApiEndpointProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.configuration + +/** + * Provides needed endpoints for EG lodging connectivity Payment GraphQL API, configured based on the specified client environment. + */ +class PaymentApiEndpointProvider private constructor() { + companion object { + /** + * Returns an [ApiEndpoint] configured for the specified environment. + * + * This method selects the appropriate API and authentication endpoints based on the given + * [ClientEnvironment] to ensure compatibility with different environments (e.g., PROD, TEST). + * + * @param environment The [ClientEnvironment] specifying the target environment (e.g., PROD, TEST). + * @return An [ApiEndpoint] containing the appropriate endpoints for the specified environment. + */ + @JvmStatic + fun forEnvironment(environment: ClientEnvironment): ApiEndpoint { + return ApiEndpoint( + endpoint = EndpointProvider.getPaymentApiEndpoint(environment), + authEndpoint = EndpointProvider.getAuthEndpoint(environment) + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SandboxApiEndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SandboxApiEndpointProvider.kt new file mode 100644 index 00000000..11f911ba --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SandboxApiEndpointProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.configuration + +/** + * Provides needed endpoints for EG lodging connectivity Sandbox GraphQL API, configured based on the specified client environment. + */ +class SandboxApiEndpointProvider private constructor() { + companion object { + /** + * Returns an [ApiEndpoint] configured for the specified environment. + * + * This method selects the appropriate API and authentication endpoints based on the given + * [ClientEnvironment] to ensure compatibility with different environments (e.g., SANDBOX_PROD, SANDBOX_TEST). + * + * @param environment The [ClientEnvironment] specifying the target environment (e.g., SANDBOX_PROD, SANDBOX_TEST). + * @return An [ApiEndpoint] containing the appropriate endpoints for the specified environment. + */ + @JvmStatic + fun forEnvironment(environment: ClientEnvironment): ApiEndpoint { + return ApiEndpoint( + endpoint = EndpointProvider.getSandboxApiEndpoint(environment), + authEndpoint = EndpointProvider.getAuthEndpoint(environment) + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SupplyApiEndpointProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SupplyApiEndpointProvider.kt new file mode 100644 index 00000000..2dda562f --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/SupplyApiEndpointProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.configuration + +/** + * Provides needed endpoints for EG lodging connectivity Supply GraphQL API, configured based on the specified client environment. + */ +class SupplyApiEndpointProvider private constructor() { + companion object { + /** + * Returns an [ApiEndpoint] configured for the specified environment. + * + * This method selects the appropriate API and authentication endpoints based on the given + * [ClientEnvironment] to ensure compatibility with different environments (e.g., PROD, TEST). + * + * @param environment The [ClientEnvironment] specifying the target environment (e.g., PROD, TEST). + * @return An [ApiEndpoint] containing the appropriate endpoints for the specified environment. + */ + @JvmStatic + fun forEnvironment(environment: ClientEnvironment): ApiEndpoint { + return ApiEndpoint( + endpoint = EndpointProvider.getSupplyApiEndpoint(environment), + authEndpoint = EndpointProvider.getAuthEndpoint(environment) + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt index c2352d20..e3f72c15 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt @@ -3,7 +3,9 @@ package com.expediagroup.sdk.lodgingconnectivity.filemanagement.client import com.expediagroup.sdk.core.client.SdkClient import com.expediagroup.sdk.core.configuration.DefaultClientBuilder import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.configuration.FileManagementApiEndpointProvider import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.FileUploadRequest import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.Upload201Response import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileDownloadOperation @@ -20,8 +22,9 @@ class FileManagementClient( ) { private val client = SdkClient( configuration = configuration.toFullClientConfiguration( - endpointProvider = EndpointProvider::getFileManagementClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint + FileManagementApiEndpointProvider.forEnvironment( + environment = configuration.environment ?: ClientEnvironment.PROD + ) ) ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt index 60dd2a1a..63d9532c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt @@ -3,10 +3,7 @@ package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models import java.io.File import java.io.InputStream -class FileUploadRequest private constructor( - val content: Any? -) { - +class FileUploadRequest private constructor(val content: Any?) { constructor(file: File) : this(content = file) constructor(inputStream: InputStream) : this(content = inputStream) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt deleted file mode 100644 index 26864317..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/BaseGraphQLClient.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql - -import com.apollographql.apollo.api.Mutation -import com.apollographql.apollo.api.Query -import com.apollographql.java.client.ApolloClient -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException -import com.expediagroup.sdk.core.configuration.DefaultClientBuilder -import com.expediagroup.sdk.core.configuration.FullClientConfiguration -import com.expediagroup.sdk.core.client.ApiClientApolloHttpEngine -import com.expediagroup.sdk.core.client.util.createApiClient -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import java.util.concurrent.CompletableFuture - -class BaseGraphQLClient(configuration: FullClientConfiguration) : GraphQLExecutor { - private val engine: ApiClientApolloHttpEngine = ApiClientApolloHttpEngine( - createApiClient( - configuration = configuration - ) - ) - - companion object { - @JvmStatic - fun builder() = ClientConfiguration.builder() - } - - private val apolloClient: ApolloClient = ApolloClient.Builder() - .serverUrl(configuration.getEndpoint()) - .httpEngine(engine) - .build() - - class Builder() : DefaultClientBuilder() { - override fun build(): BaseGraphQLClient { - return BaseGraphQLClient(this.buildConfiguration()) - } - } - - /** - * Executes a GraphQL query and returns the result. - * - * @param query The GraphQL query to execute. - * @return The result of the query execution, with errors handled. - * @throws ExpediaGroupServiceException If the query execution returns errors. - */ - override fun executeAsync(query: Query): CompletableFuture { - val promise: CompletableFuture = CompletableFuture() - - apolloClient.query(query).enqueue { response -> - try { - if (response.hasErrors()) { - // Complete exceptionally if there are GraphQL errors - promise.completeExceptionally(ExpediaGroupServiceException(message = response.errors.toString())) - } else if (response.exception != null) { - // Complete exceptionally if there is a network or other exception - promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) - } else { - // Complete normally with the response data if no errors or exceptions - promise.complete(response.dataAssertNoErrors) - } - } catch (e: Exception) { - // Handle unexpected exceptions during callback execution - promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) - } - } - - return promise - } - - override fun execute(query: Query): T = - executeAsync(query).get() - - - /** - * Executes a GraphQL mutation and returns the result. - * - * @param mutation The GraphQL mutation to execute. - * @return The result of the mutation execution, with errors handled. - * @throws ExpediaGroupServiceException If the mutation execution returns errors. - */ - override fun executeAsync(mutation: Mutation): CompletableFuture { - val promise: CompletableFuture = CompletableFuture() - - apolloClient.mutation(mutation).enqueue { response -> - try { - if (response.hasErrors()) { - // Complete exceptionally if there are GraphQL errors - promise.completeExceptionally(ExpediaGroupServiceException(response.errors.toString())) - } else if (response.exception != null) { - // Complete exceptionally if there is a network or other exception - promise.completeExceptionally(ExpediaGroupServiceException(cause = response.exception)) - } else { - // Complete normally with the response data if no errors or exceptions - promise.complete(response.dataAssertNoErrors) - } - } catch (e: Exception) { - // Handle unexpected exceptions during callback execution - promise.completeExceptionally(ExpediaGroupServiceException(cause = e)) - } - } - - return promise - } - - override fun execute(mutation: Mutation): T = - executeAsync(mutation).get() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt deleted file mode 100644 index b1bf39e7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/DateTimeAdapter.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql.adapter - -import com.apollographql.apollo.api.Adapter -import com.apollographql.apollo.api.CustomScalarAdapters -import com.apollographql.apollo.api.json.JsonReader -import com.apollographql.apollo.api.json.JsonWriter -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -/** - * Converts the custom scalar `DateTime` to and from `java.time.OffsetDateTime`. - */ -object DateTimeAdapter : Adapter { - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): OffsetDateTime? { - val dateString = reader.nextString() ?: return null - return OffsetDateTime.parse(dateString, DateTimeFormatter.ISO_OFFSET_DATE_TIME) - } - - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: OffsetDateTime?) { - if (value != null) { - writer.value(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) - } else { - writer.nullValue() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/URLAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/URLAdapter.kt index 7c99f060..49bd7aa3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/URLAdapter.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/URLAdapter.kt @@ -26,14 +26,13 @@ import java.net.URISyntaxException import java.net.URL /** - * Converts the custom scalar `Url` to and from `java.net.URL`. + * Converts the GraphQL custom scalar `Url` to and from `java.net.URL`. */ -object URLAdapter : Adapter { - - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): URL? { - val urlString = reader.nextString() ?: return null +class URLAdapter : Adapter { + override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): URL { + val urlString = reader.nextString() return try { - URI(urlString).toURL() + URI(urlString!!).toURL() } catch (e: URISyntaxException) { throw IllegalStateException("Invalid URI format: $urlString", e) } catch (e: MalformedURLException) { @@ -41,11 +40,7 @@ object URLAdapter : Adapter { } } - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: URL?) { - if (value != null) { - writer.value(value.toString()) - } else { - writer.nullValue() - } + override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: URL) { + writer.value(value.toString()) } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt deleted file mode 100644 index 570cbf16..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/adapter/ZoneDateTimeAdapter.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql.adapter - -import com.apollographql.apollo.api.Adapter -import com.apollographql.apollo.api.CustomScalarAdapters -import com.apollographql.apollo.api.json.JsonReader -import com.apollographql.apollo.api.json.JsonWriter -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -/** - * Converts ZoneDateTime custom scalar `Url` to and from `java.time.format.DateTimeFormatter`. - */ -object ZoneDateTimeAdapter : Adapter { - override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): ZonedDateTime? { - val dateString = reader.nextString() ?: return null - return ZonedDateTime.parse(dateString, DateTimeFormatter.ISO_ZONED_DATE_TIME) - } - - override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: ZonedDateTime?) { - if (value != null) { - writer.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) - } else { - writer.nullValue() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt deleted file mode 100644 index b7648c47..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/payment/PaymentClient.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql.payment - -import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * @constructor Creates a new instance of `PaymentClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * PaymentClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class PaymentClient(config: ClientConfiguration): - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getPaymentClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint - ), - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt deleted file mode 100644 index 8b1d4376..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/sandbox/SandboxDataManagementClient.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox - -import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Sandbox GraphQL API. - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * @constructor Creates a new instance of `SandboxDataManagementClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * SandboxDataManagementClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class SandboxDataManagementClient(config: ClientConfiguration) : - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getSandboxDataManagementClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint, - defaultEnvironment = ClientEnvironment.SANDBOX_PROD - ), - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt deleted file mode 100644 index c97e057e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/supply/SupplyClient.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.graphql.supply - -import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.graphql.BaseGraphQLClient -import com.expediagroup.sdk.lodgingconnectivity.graphql.GraphQLExecutor - -/** - * A client for interacting with EG Lodging Connectivity Supply GraphQL API that exposes various lodging capabilities - * such as reservations, promotions, reviews, notifications, messaging, etc... - * - * This client is configured with a `ClientConfiguration` that includes authentication details, - * and it automatically determines the appropriate API endpoints based on the environment (e.g., production or test). - * - * In addition, this client can be configured to target the sandbox environment by passing `ClientEnvironment.SANDBOX_PROD` or - * `ClientEnvironment.SANDBOX_TEST` to the `environment` configuration option. - * - * @constructor Creates a new instance of `SupplyClient` using the provided configuration. - * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment, - * timeouts, and logging masking options. - * - * Example usage: - * ``` - * SupplyClient( - * ClientConfiguration - * .builder() - * .key("API_KEY") - * .secret("API_SECRET") - * .build() - * ) - * ``` - */ -class SupplyClient(config: ClientConfiguration) : - GraphQLExecutor by BaseGraphQLClient( - config.toFullClientConfiguration( - endpointProvider = EndpointProvider::getSupplyClientEndpoint, - authEndpointProvider = EndpointProvider::getAuthEndpoint - ), - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt new file mode 100644 index 00000000..906c1b19 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.payment + +import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor +import com.expediagroup.sdk.graphql.common.GraphQLClient +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.PaymentApiEndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.payment.operation.PaymentInstrumentQuery +import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInstrumentOperation + +/** + * A client for interacting with EG Lodging Connectivity Payment PCI GraphQL API. + * + * Endpoint is automatically determined based on the environment configuration (e.g., [ClientEnvironment.PROD] or [ClientEnvironment.TEST]) + * + * @constructor Creates a new instance of `PaymentClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment + * or timeouts. + */ +class PaymentClient(config: ClientConfiguration) : GraphQLClient() { + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( + config.toFullClientConfiguration( + apiEndpoint = PaymentApiEndpointProvider.forEnvironment( + environment = config.environment ?: ClientEnvironment.PROD + ), + ) + ) + + /** + * Retrieves the payment instrument details associated with the specified token. + * + * This function executes a [PaymentInstrumentQuery] GraphQL operation using the client’s configured + * [GraphQLExecutor]. It returns a [GetPaymentInstrumentResponse] containing both the targeted payment + * instrument data and the full raw response. + * + * @param token The token identifying the payment instrument to be retrieved. + * @return A [GetPaymentInstrumentResponse] containing the requested payment instrument data and the full raw response. + * @throws ExpediaGroupServiceException If the payment instrument data is not found in the response. + */ + fun getPaymentInstrument(token: String) = run { + getPaymentInstrumentOperation(graphQLExecutor, token) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/operation/GetPaymentInstrumentOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/operation/GetPaymentInstrumentOperation.kt new file mode 100644 index 00000000..6b5a6c70 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/operation/GetPaymentInstrumentOperation.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.payment.operation + +import com.expediagroup.sdk.core.extension.getOrThrow +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.payment.operation.fragment.PaymentInstrumentData + +/** + * Represents the response for a [PaymentInstrumentQuery] GraphQL operation, containing both + * the processed payment instrument data and the full raw GraphQL response. + * + * @param data The processed [PaymentInstrumentData] extracted from the raw response, representing + * the details of the payment instrument. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure + * and any associated errors. + */ +data class GetPaymentInstrumentResponse( + override val data: PaymentInstrumentData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [PaymentInstrumentQuery] GraphQL operation to retrieve details about a specific payment instrument. + * + * This function uses the provided [GraphQLExecutor] to execute the operation and returns a [GetPaymentInstrumentResponse] + * containing both the targeted payment instrument data and the full raw response. If the payment instrument + * data is missing or null, an [ExpediaGroupServiceException] is thrown. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param token The token identifying the payment instrument to be retrieved. + * @return A [GetPaymentInstrumentResponse] containing the requested payment instrument data and the full raw response. + * @throws ExpediaGroupServiceException If the payment instrument data is not found in the response. + */ +fun getPaymentInstrumentOperation(graphQLExecutor: GraphQLExecutor, token: String): GetPaymentInstrumentResponse { + val operation = PaymentInstrumentQuery(token) + val response = graphQLExecutor.execute(operation) + + val paymentInstrument = response.data.paymentInstrument.getOrThrow { + ExpediaGroupServiceException("Couldn't fetch payment instrument") + } + + return GetPaymentInstrumentResponse( + data = paymentInstrument.paymentInstrumentData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt new file mode 100644 index 00000000..fdbdb49d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox + +import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor +import com.expediagroup.sdk.graphql.common.GraphQLClient +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.SandboxApiEndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CancelReservationInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreatePropertyInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreateReservationInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeletePropertyInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeletePropertyReservationsInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeleteReservationInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdatePropertyInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdateReservationInput +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.CreateSandboxPropertyResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.DeleteSandboxPropertyResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.GetSandboxPropertiesResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.GetSandboxPropertyResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.UpdateSandboxPropertyResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.createSandboxPropertyOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.deleteSandboxPropertyOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.getSandboxPropertiesOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.getSandboxPropertyOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.updateSandboxPropertyOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.paginator.SandboxPropertiesPaginator +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.CancelSandboxReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.ChangeSandboxReservationStayDatesResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.CreateSandboxReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.DeleteSandboxReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.DeleteSandboxReservationsResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.GetSandboxReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.GetSandboxReservationsResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.UpdateSandboxReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.cancelSandboxReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.changeSandboxReservationStayDatesOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.createSandboxReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.deleteSandboxReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.deleteSandboxReservationsOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.getSandboxReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.getSandboxReservationsOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.updateSandboxReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator.SandboxReservationsPaginator + +/** + * A client for interacting with EG Lodging Connectivity Sandbox GraphQL API. + * + * The `SandboxDataManagementClient` provides a comprehensive API for creating, retrieving, updating, and deleting + * sandbox properties and reservations. + * + * Endpoint is automatically determined based on the environment configuration (e.g., [ClientEnvironment.SANDBOX_PROD] or [ClientEnvironment.SANDBOX_TEST]) + * + * @constructor Creates a new instance of `SandboxDataManagementClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment + * or timeouts. + */ +class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() { + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( + config.toFullClientConfiguration( + apiEndpoint = SandboxApiEndpointProvider.forEnvironment( + environment = config.environment ?: ClientEnvironment.SANDBOX_PROD + ), + ) + ) + + /** + * Retrieves all sandbox properties in one page. + * + * @return A [GetSandboxPropertiesResponse] containing the sandbox properties data, pagination information, and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun getProperties() = run { + getSandboxPropertiesOperation(graphQLExecutor) + } + + /** + * Creates a paginator for fetching sandbox properties page-by-page. + * + * @param pageSize The number of properties to retrieve per page. + * @param initialCursor An optional initial cursor to specify the starting point. + * @return A [SandboxPropertiesPaginator] for iterating through properties. + */ + @JvmOverloads + fun getPropertiesPaginator(pageSize: Int, initialCursor: String? = null) = run { + SandboxPropertiesPaginator(graphQLExecutor, pageSize, initialCursor) + } + + /** + * Retrieves all reservations for a specific sandbox property. + * + * @param propertyId The unique identifier of the property. + * @return A [GetSandboxReservationsResponse] containing the sandbox reservations data, pagination information, and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun getReservations(propertyId: String) = run { + getSandboxReservationsOperation(graphQLExecutor, propertyId) + } + + /** + * Creates a paginator for fetching reservations page-by-page for a specific sandbox property. + * + * @param propertyId The unique identifier of the sandbox property. + * @param pageSize The number of reservations to retrieve per page. + * @param initialCursor An optional initial cursor to specify the starting point. + * @return A [SandboxReservationsPaginator] for iterating through reservations. + */ + @JvmOverloads + fun getReservationsPaginator(propertyId: String, pageSize: Int, initialCursor: String? = null) = run { + SandboxReservationsPaginator(graphQLExecutor, propertyId, pageSize, initialCursor) + } + + /** + * Retrieves details of a specific sandbox property. + * + * @param propertyId The unique identifier of the property. + * @return A [GetSandboxPropertyResponse] containing the requested sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun getProperty(propertyId: String) = run { + getSandboxPropertyOperation(graphQLExecutor, propertyId) + } + + /** + * Retrieves details of a specific sandbox reservation. + * + * @param reservationId The unique identifier of the reservation. + * @return A [GetSandboxReservationResponse] containing the requested reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun getReservation(reservationId: String) = run { + getSandboxReservationOperation(graphQLExecutor, reservationId) + } + + /** + * Creates a new sandbox property with default name. + * + * @return A [CreateSandboxPropertyResponse] containing the created sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun createProperty() = run { + createSandboxPropertyOperation(graphQLExecutor, CreatePropertyInput()) + } + + /** + * Creates a new sandbox property with the specified input. + * + * @param input The [CreatePropertyInput] specifying the details of the property to be created. + * @return A [CreateSandboxPropertyResponse] containing the created sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun createProperty(input: CreatePropertyInput) = run { + createSandboxPropertyOperation(graphQLExecutor, input) + } + + /** + * Updates an existing sandbox property. + * + * @param input The [UpdatePropertyInput] containing the updated property details. + * @return An [UpdateSandboxPropertyResponse] containing the updated sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun updateProperty(input: UpdatePropertyInput) = run { + updateSandboxPropertyOperation(graphQLExecutor, input) + } + + /** + * Deletes a specified sandbox property. + * + * @param propertyId The unique identifier of the property to be deleted. + * @return A [DeleteSandboxPropertyResponse] containing the deleted property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun deleteProperty(propertyId: String) = run { + deleteSandboxPropertyOperation(graphQLExecutor, DeletePropertyInput(id = propertyId)) + } + + /** + * Creates a new sandbox reservation for a specific property. + * + * @param propertyId The unique identifier of the property. + * @return A [CreateSandboxReservationResponse] containing the created reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun createReservation(propertyId: String) = run { + createSandboxReservationOperation(graphQLExecutor, CreateReservationInput(propertyId = propertyId)) + } + + /** + * Creates a new sandbox reservation with the specified input. + * + * @param input The [CreateReservationInput] containing the reservation details. + * @return A [CreateSandboxReservationResponse] containing the created reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun createReservation(input: CreateReservationInput) = run { + createSandboxReservationOperation(graphQLExecutor, input) + } + + /** + * Updates an existing sandbox reservation. + * + * @param input The [UpdateReservationInput] with the updated reservation details. + * @return An [UpdateSandboxReservationResponse] containing the updated reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun updateReservation(input: UpdateReservationInput) = run { + updateSandboxReservationOperation(graphQLExecutor, input) + } + + /** + * Changes the stay dates for an existing sandbox reservation. + * + * @param input The [ChangeReservationStayDatesInput] specifying the new stay dates. + * @return A [ChangeSandboxReservationStayDatesResponse] containing the updated reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun changeReservationStayDates(input: ChangeReservationStayDatesInput) = run { + changeSandboxReservationStayDatesOperation(graphQLExecutor, input) + } + + /** + * Cancels a specific sandbox reservation. + * + * @param reservationId The unique identifier of the reservation to be canceled. + * @return A [CancelSandboxReservationResponse] containing the canceled reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun cancelReservation(reservationId: String) = run { + cancelSandboxReservationOperation(graphQLExecutor, CancelReservationInput(id = reservationId)) + } + + /** + * Cancels a sandbox reservation with the specified input. + * + * @param input The [CancelReservationInput] containing reservation details for cancellation. + * @return A [CancelSandboxReservationResponse] containing the canceled reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun cancelReservation(input: CancelReservationInput) = run { + cancelSandboxReservationOperation(graphQLExecutor, input) + } + + /** + * Deletes a specific sandbox reservation. + * + * @param reservationId The unique identifier of the reservation to be deleted. + * @return A [DeleteSandboxReservationResponse] containing the deleted reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun deleteReservation(reservationId: String) = run { + deleteSandboxReservationOperation(graphQLExecutor, DeleteReservationInput(id = reservationId)) + } + + /** + * Deletes a sandbox reservation with the specified input. + * + * @param input The [DeleteReservationInput] specifying the reservation to delete. + * @return A [DeleteSandboxReservationResponse] containing the deleted reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun deleteReservation(input: DeleteReservationInput) = run { + deleteSandboxReservationOperation(graphQLExecutor, input) + } + + /** + * Deletes all reservations for a specified property. + * + * @param propertyId The unique identifier of the property. + * @return A [DeleteSandboxReservationsResponse] containing data for the deleted reservations and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun deleteReservations(propertyId: String) = run { + deleteSandboxReservationsOperation( + graphQLExecutor, + DeletePropertyReservationsInput(propertyId = propertyId) + ) + } + + /** + * Deletes all reservations for a specified property. + * + * @param input The [DeletePropertyReservationsInput] specifying the reservations to delete. + * @return A [DeleteSandboxReservationsResponse] containing data for the deleted reservations and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + fun deleteReservations(input: DeletePropertyReservationsInput) = run { + deleteSandboxReservationsOperation(graphQLExecutor, input) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/CreateSandboxPropertyOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/CreateSandboxPropertyOperation.kt new file mode 100644 index 00000000..93b37411 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/CreateSandboxPropertyOperation.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxCreatePropertyMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxPropertyData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreatePropertyInput + +/** + * Represents the response for [SandboxCreatePropertyMutation] GraphQL operation, containing both the processed + * sandbox property data and the full raw GraphQL response. + * + * @param data The processed [SandboxPropertyData] extracted from the raw response, representing the created sandbox property details. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class CreateSandboxPropertyResponse( + override val data: SandboxPropertyData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes a [SandboxCreatePropertyMutation] GraphQL mutation to create a new sandbox property with the specified input data. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CreateSandboxPropertyResponse] + * containing both the targeted sandbox property data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CreatePropertyInput] containing the details for the property to be created. + * @return A [CreateSandboxPropertyResponse] containing the created sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun createSandboxPropertyOperation( + graphQLExecutor: GraphQLExecutor, + input: CreatePropertyInput +): CreateSandboxPropertyResponse { + val operation = SandboxCreatePropertyMutation(input) + val response = graphQLExecutor.execute(operation) + + return CreateSandboxPropertyResponse( + data = response.data.createProperty.property.sandboxPropertyData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/DeleteSandboxPropertyOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/DeleteSandboxPropertyOperation.kt new file mode 100644 index 00000000..7bb48b99 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/DeleteSandboxPropertyOperation.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxDeletePropertyMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeletePropertyInput + +/** + * Represents the response for [SandboxDeletePropertyMutation] GraphQL operation, containing both the processed + * delete property data and the full raw GraphQL response. + * + * @param data The processed [SandboxDeletePropertyMutation.DeleteProperty] data extracted from the raw response, + * representing details of the deleted sandbox property. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class DeleteSandboxPropertyResponse( + override val data: SandboxDeletePropertyMutation.DeleteProperty, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxDeletePropertyMutation] GraphQL mutation to delete an existing sandbox property with the specified input data. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [DeleteSandboxPropertyResponse] + * containing both the targeted delete property data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [DeletePropertyInput] containing the details of the property to be deleted. + * @return A [DeleteSandboxPropertyResponse] containing the deleted property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun deleteSandboxPropertyOperation( + graphQLExecutor: GraphQLExecutor, + input: DeletePropertyInput +): DeleteSandboxPropertyResponse { + val operation = SandboxDeletePropertyMutation(input) + val response = graphQLExecutor.execute(operation) + + return DeleteSandboxPropertyResponse( + data = response.data.deleteProperty, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertiesOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertiesOperation.kt new file mode 100644 index 00000000..6126ac96 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertiesOperation.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation + +import com.expediagroup.sdk.core.extension.orNullIfBlank +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertiesQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxPropertyData + +/** + * Represents the paginated response for [SandboxPropertiesQuery] GraphQL operation, containing the list of sandbox + * properties, pagination information, and the full raw GraphQL response. + * + * @param data A list of [SandboxPropertyData] representing the sandbox properties returned in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class GetSandboxPropertiesResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, SandboxPropertiesQuery.Data> + + +/** + * Executes a [SandboxPropertiesQuery] GraphQL query to retrieve a paginated list of sandbox properties. + * + * This function uses the provided [GraphQLExecutor] to execute the query, with optional parameters for cursor and page size. + * It returns a [GetSandboxPropertiesResponse] containing the sandbox properties data, pagination information, + * and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param cursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @param pageSize The number of properties to retrieve per page; defaults to `null` to use the server's default page size. + * @return A [GetSandboxPropertiesResponse] containing the sandbox properties data, pagination information, and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ +@JvmOverloads +fun getSandboxPropertiesOperation( + graphQLExecutor: GraphQLExecutor, + cursor: String? = null, + pageSize: Int? = null +): GetSandboxPropertiesResponse { + val operation = SandboxPropertiesQuery + .builder() + .pageSize(pageSize) + .cursor(cursor) + .build() + + val response = graphQLExecutor.execute(operation) + + val nextPageCursor = response.data.properties.cursor.orNullIfBlank() + + val currentPageInfo = PageInfo( + cursor = cursor, + nextPageCursor = nextPageCursor, + hasNext = nextPageCursor != null, + pageSize = response.data.properties.elements.size, + totalCount = response.data.properties.totalCount + ) + + return GetSandboxPropertiesResponse( + data = response.data.properties.elements.map { it.sandboxPropertyData }, + rawResponse = response, + pageInfo = currentPageInfo + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertyOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertyOperation.kt new file mode 100644 index 00000000..25de2134 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/GetSandboxPropertyOperation.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertyQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxPropertyData + +/** + * Represents the response for [SandboxPropertyQuery] GraphQL operation, containing both the processed + * sandbox property data and the full raw GraphQL response. + * + * @param data The processed [SandboxPropertyData] extracted from the raw response, representing + * details of the requested sandbox property. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure + * and any associated errors. + */ +data class GetSandboxPropertyResponse( + override val data: SandboxPropertyData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes a [SandboxPropertyQuery] GraphQL query to retrieve details about a specific sandbox property. + * + * This function uses the provided [GraphQLExecutor] to execute the query and returns a [GetSandboxPropertyResponse] + * containing both the targeted sandbox property data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param propertyId The unique identifier of the sandbox property to retrieve. + * @return A [GetSandboxPropertyResponse] containing the requested sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ +fun getSandboxPropertyOperation(graphQLExecutor: GraphQLExecutor, propertyId: String): GetSandboxPropertyResponse { + val operation = SandboxPropertyQuery(propertyId) + val response = graphQLExecutor.execute(operation) + + return GetSandboxPropertyResponse( + data = response.data.property.sandboxPropertyData, + rawResponse = response, + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/UpdateSandboxPropertyOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/UpdateSandboxPropertyOperation.kt new file mode 100644 index 00000000..60f13a67 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/operation/UpdateSandboxPropertyOperation.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxUpdatePropertyMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxPropertyData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdatePropertyInput + +/** + * Represents the response for [SandboxUpdatePropertyMutation] GraphQL operation, containing both the processed + * sandbox property data and the full raw GraphQL response. + * + * @param data The updated [SandboxPropertyData] extracted from the raw response, representing the details + * of the sandbox property after the update. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure + * and any associated errors. + */ +data class UpdateSandboxPropertyResponse( + override val data: SandboxPropertyData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxUpdatePropertyMutation] GraphQL mutation to modify the details of an existing sandbox property. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns an [UpdateSandboxPropertyResponse] + * containing both the updated sandbox property data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [UpdatePropertyInput] containing the details of the property update. + * @return An [UpdateSandboxPropertyResponse] containing the updated sandbox property data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun updateSandboxPropertyOperation( + graphQLExecutor: GraphQLExecutor, + input: UpdatePropertyInput +): UpdateSandboxPropertyResponse { + val operation = SandboxUpdatePropertyMutation(input) + val response = graphQLExecutor.execute(operation) + + return UpdateSandboxPropertyResponse( + data = response.data.updateProperty.property.sandboxPropertyData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/paginator/SandboxPropertiesPaginator.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/paginator/SandboxPropertiesPaginator.kt new file mode 100644 index 00000000..349a3edb --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/property/paginator/SandboxPropertiesPaginator.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.property.paginator + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertiesQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertiesTotalCountQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxPropertyData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.getSandboxPropertiesOperation + +/** + * Represents a paginated response for [SandboxPropertiesQuery] GraphQL operation, containing + * a list of sandbox properties, pagination details, and the full raw GraphQL response. + * + * @param data A list of [SandboxPropertyData] representing the sandbox properties in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class SandboxPropertiesPaginatedResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, SandboxPropertiesQuery.Data> + +/** + * Provides an iterator to retrieve sandbox properties in a paginated manner using the [SandboxPropertiesQuery] + * GraphQL operation, allowing seamless iteration over pages of properties. + * + * This paginator uses the specified [GraphQLExecutor] to fetch pages based on a cursor and optional page size, + * providing automatic handling of pagination state. + * + * @param graphQLExecutor The [GraphQLExecutor] used to execute GraphQL queries. + * @param pageSize The number of properties to retrieve per page; defaults to `null` to use the server's default page size. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @constructor Creates a [SandboxPropertiesPaginator] with the specified executor, page size, and initial cursor. + */ +class SandboxPropertiesPaginator @JvmOverloads constructor( + private val graphQLExecutor: GraphQLExecutor, + private val pageSize: Int? = null, + initialCursor: String? = null +) : Iterator { + private var cursor: String? = initialCursor + private var hasNext: Boolean = true + private var initialized: Boolean = false + + /** + * Checks if there are more pages to fetch. + * + * This method returns `true` if additional pages are available; otherwise, it returns `false`. + * It initializes the paginator by checking if there are properties to fetch when called for the first time. + * + * @return `true` if there are more pages to fetch, `false` otherwise. + */ + override fun hasNext(): Boolean { + if (!initialized) { + initialized = true + return hasPropertiesToFetch() + } + + return hasNext + } + + /** + * Retrieves the next page of sandbox properties. + * + * This method executes a [SandboxPropertiesQuery] query to fetch the next page of properties, + * updating the pagination state and cursor for subsequent requests. + * + * @return A [SandboxPropertiesPaginatedResponse] containing the sandbox properties, raw response, and pagination details. + * @throws NoSuchElementException If no more pages are available to fetch. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ + override fun next(): SandboxPropertiesPaginatedResponse { + if (!hasNext()) { + throw NoSuchElementException("No more pages to fetch") + } + + val response = getSandboxPropertiesOperation( + graphQLExecutor = graphQLExecutor, + cursor = cursor, + pageSize = pageSize + ) + + cursor = response.pageInfo.nextPageCursor + hasNext = response.pageInfo.hasNext + + return SandboxPropertiesPaginatedResponse( + data = response.data, + rawResponse = response.rawResponse, + pageInfo = response.pageInfo + ) + } + + /** + * Checks if there are any properties available to fetch, initializing the paginator if necessary. + * + * @return `true` if there are properties available, `false` otherwise. + */ + private fun hasPropertiesToFetch(): Boolean = run { + graphQLExecutor.execute( + SandboxPropertiesTotalCountQuery() + ).let { + it.data.properties.totalCount > 0 + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CancelSandboxReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CancelSandboxReservationOperation.kt new file mode 100644 index 00000000..b495aca4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CancelSandboxReservationOperation.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxCancelReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CancelReservationInput + +/** + * Represents the response for [SandboxCancelReservationMutation] GraphQL operation, containing both the processed + * sandbox reservation data and the full raw GraphQL response. + * + * @param data The [SandboxReservationData] extracted from the raw response, representing details + * of the sandbox reservation after cancellation. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure + * and any associated errors. + */ +data class CancelSandboxReservationResponse( + override val data: SandboxReservationData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxCancelReservationMutation] GraphQL mutation to cancel an existing sandbox reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CancelSandboxReservationResponse] + * containing both the updated reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CancelReservationInput] containing the details of the reservation to be canceled. + * @return A [CancelSandboxReservationResponse] containing the canceled reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun cancelSandboxReservationOperation(graphQLExecutor: GraphQLExecutor, input: CancelReservationInput): CancelSandboxReservationResponse { + val operation = SandboxCancelReservationMutation(input) + val response = graphQLExecutor.execute(operation) + + return CancelSandboxReservationResponse( + data = response.data.cancelReservation.reservation.sandboxReservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/ChangeSandboxReservationStayDatesOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/ChangeSandboxReservationStayDatesOperation.kt new file mode 100644 index 00000000..02167d73 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/ChangeSandboxReservationStayDatesOperation.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxChangeReservationStayDatesMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput + +/** + * Represents the response for [SandboxChangeReservationStayDatesMutation] GraphQL operation, containing both the processed + * sandbox reservation data and the full raw GraphQL response. + * + * @param data The updated [SandboxReservationData] extracted from the raw response, representing details + * of the sandbox reservation after changing the stay dates. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure + * and any associated errors. + */ +data class ChangeSandboxReservationStayDatesResponse( + override val data: SandboxReservationData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxChangeReservationStayDatesMutation] GraphQL mutation to modify the stay dates of an existing sandbox reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [ChangeSandboxReservationStayDatesResponse] + * containing both the updated reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [ChangeReservationStayDatesInput] containing the new stay dates for the reservation. + * @return A [ChangeSandboxReservationStayDatesResponse] containing the updated reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun changeSandboxReservationStayDatesOperation( + graphQLExecutor: GraphQLExecutor, + input: ChangeReservationStayDatesInput +): ChangeSandboxReservationStayDatesResponse { + val operation = SandboxChangeReservationStayDatesMutation(input) + val response = graphQLExecutor.execute(operation) + + return ChangeSandboxReservationStayDatesResponse( + data = response.data.changeReservationStayDates.reservation.sandboxReservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CreateSandboxReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CreateSandboxReservationOperation.kt new file mode 100644 index 00000000..438853ea --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/CreateSandboxReservationOperation.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxCreateReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreateReservationInput + +/** + * Represents the response for [SandboxCreateReservationMutation] GraphQL operation, containing both the processed + * sandbox reservation data and the full raw GraphQL response. + * + * @param data The [SandboxReservationData] extracted from the raw response, representing details of the newly created sandbox reservation. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class CreateSandboxReservationResponse( + override val data: SandboxReservationData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxCreateReservationMutation] GraphQL mutation to create a new sandbox reservation with the specified input data. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CreateSandboxReservationResponse] + * containing both the created reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CreateReservationInput] containing the details for the new reservation. + * @return A [CreateSandboxReservationResponse] containing the created reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun createSandboxReservationOperation(graphQLExecutor: GraphQLExecutor, input: CreateReservationInput): CreateSandboxReservationResponse { + val operation = SandboxCreateReservationMutation(input) + val response = graphQLExecutor.execute(operation) + + return CreateSandboxReservationResponse( + data = response.data.createReservation.reservation.sandboxReservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationOperation.kt new file mode 100644 index 00000000..f48e9210 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationOperation.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxDeleteReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeleteReservationInput + +/** + * Represents the response for [SandboxDeleteReservationMutation] GraphQL operation, containing both the processed + * delete reservation data and the full raw GraphQL response. + * + * @param data The [SandboxDeleteReservationMutation.DeleteReservation] extracted from the raw response, representing + * details of the deleted sandbox reservation. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class DeleteSandboxReservationResponse( + override val data: SandboxDeleteReservationMutation.DeleteReservation, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxDeleteReservationMutation] GraphQL mutation to remove an existing sandbox reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [DeleteSandboxReservationResponse] + * containing both the deleted reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [DeleteReservationInput] containing the details of the reservation to be deleted. + * @return A [DeleteSandboxReservationResponse] containing the deleted reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun deleteSandboxReservationOperation( + graphQLExecutor: GraphQLExecutor, + input: DeleteReservationInput +): DeleteSandboxReservationResponse { + val operation = SandboxDeleteReservationMutation(input) + val response = graphQLExecutor.execute(operation) + + return DeleteSandboxReservationResponse( + data = response.data.deleteReservation, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationsOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationsOperation.kt new file mode 100644 index 00000000..6080bff7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/DeleteSandboxReservationsOperation.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxDeletePropertyReservationsMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.DeletePropertyReservationsInput + +/** + * Represents the response for [SandboxDeletePropertyReservationsMutation] GraphQL operation, containing both the processed + * deletion data for multiple reservations and the full raw GraphQL response. + * + * @param data The [SandboxDeletePropertyReservationsMutation.DeletePropertyReservations] extracted from the raw response, + * representing details of the deleted reservations. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class DeleteSandboxReservationsResponse( + override val data: SandboxDeletePropertyReservationsMutation.DeletePropertyReservations, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxDeletePropertyReservationsMutation] GraphQL mutation to remove multiple reservations for a specified property. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [DeleteSandboxReservationsResponse] + * containing both the data for the deleted reservations and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [DeletePropertyReservationsInput] containing the details of the reservations to be deleted. + * @return A [DeleteSandboxReservationsResponse] containing data for the deleted reservations and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun deleteSandboxReservationsOperation( + graphQLExecutor: GraphQLExecutor, + input: DeletePropertyReservationsInput +): DeleteSandboxReservationsResponse { + val operation = SandboxDeletePropertyReservationsMutation(input) + val response = graphQLExecutor.execute(operation) + + return DeleteSandboxReservationsResponse( + data = response.data.deletePropertyReservations, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationOperation.kt new file mode 100644 index 00000000..e88c7435 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationOperation.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxReservationQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData + +/** + * Represents the response for [SandboxReservationQuery] GraphQL operation, containing both the processed + * sandbox reservation data and the full raw GraphQL response. + * + * @param data The [SandboxReservationData] extracted from the raw response, representing details of the requested sandbox reservation. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + */ +data class GetSandboxReservationResponse( + override val data: SandboxReservationData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxReservationQuery] GraphQL query to retrieve details about a specific sandbox reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the query and returns a [GetSandboxReservationResponse] + * containing both the targeted reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param reservationId The unique identifier of the sandbox reservation to retrieve. + * @return A [GetSandboxReservationResponse] containing the requested reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ +fun getSandboxReservationOperation(graphQLExecutor: GraphQLExecutor, reservationId: String): GetSandboxReservationResponse { + val operation = SandboxReservationQuery(reservationId) + val response = graphQLExecutor.execute(operation) + + return GetSandboxReservationResponse( + data = response.data.reservation.sandboxReservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationsOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationsOperation.kt new file mode 100644 index 00000000..3652ff8a --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/GetSandboxReservationsOperation.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.core.extension.orNullIfBlank +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertyReservationsQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData + +/** + * Represents the paginated response for [SandboxPropertyReservationsQuery] GraphQL operation, containing a list + * of sandbox reservation data, pagination information, and the full raw GraphQL response. + * + * @param data A list of [SandboxReservationData] representing the sandbox reservations returned in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class GetSandboxReservationsResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, SandboxPropertyReservationsQuery.Data> + +/** + * Executes [SandboxPropertyReservationsQuery] GraphQL query to retrieve a paginated list of reservations for a specific property. + * + * This function uses the provided [GraphQLExecutor] to execute the query, with optional parameters for cursor and page size. + * It returns a [GetSandboxReservationsResponse] containing the reservation data, pagination information, and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param propertyId The unique identifier of the property for which reservations are being retrieved. + * @param cursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @param pageSize The number of reservations to retrieve per page; defaults to `null` to use the server's default page size. + * @return A [GetSandboxReservationsResponse] containing the sandbox reservations data, pagination information, and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ +@JvmOverloads +fun getSandboxReservationsOperation( + graphQLExecutor: GraphQLExecutor, + propertyId: String, + cursor: String? = null, + pageSize: Int? = null +): GetSandboxReservationsResponse { + val operation = SandboxPropertyReservationsQuery + .builder() + .propertyId(propertyId) + .cursor(cursor) + .pageSize(pageSize) + .build() + + val response = graphQLExecutor.execute(operation) + + val nextPageCursor = response.data.property.reservations.cursor.orNullIfBlank() + + val currentPageInfo = PageInfo( + cursor = cursor, + nextPageCursor = nextPageCursor, + hasNext = nextPageCursor != null, + pageSize = response.data.property.reservations.elements.size, + totalCount = response.data.property.reservations.totalCount + ) + + return GetSandboxReservationsResponse( + data = response.data.property.reservations.elements.map { it.sandboxReservationData }, + rawResponse = response, + pageInfo = currentPageInfo + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/UpdateSandboxReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/UpdateSandboxReservationOperation.kt new file mode 100644 index 00000000..19207cb0 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/operation/UpdateSandboxReservationOperation.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxUpdateReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdateReservationInput + +/** + * Represents the response for [SandboxUpdateReservationMutation] GraphQL operation, containing both the processed + * sandbox reservation data and the full raw GraphQL response. + * + * @param data The updated [SandboxReservationData] extracted from the raw response, representing details of + * the sandbox reservation after the update. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class UpdateSandboxReservationResponse( + override val data: SandboxReservationData, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [SandboxUpdateReservationMutation] GraphQL mutation to modify the details of an existing sandbox reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns an [UpdateSandboxReservationResponse] + * containing both the updated reservation data and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [UpdateReservationInput] containing the new details for the reservation. + * @return An [UpdateSandboxReservationResponse] containing the updated reservation data and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +fun updateSandboxReservationOperation( + graphQLExecutor: GraphQLExecutor, + input: UpdateReservationInput +): UpdateSandboxReservationResponse { + val operation = SandboxUpdateReservationMutation(input) + val response = graphQLExecutor.execute(operation) + + return UpdateSandboxReservationResponse( + data = response.data.updateReservation.reservation.sandboxReservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/paginator/SandboxReservationsPaginator.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/paginator/SandboxReservationsPaginator.kt new file mode 100644 index 00000000..aa6e1272 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/reservation/paginator/SandboxReservationsPaginator.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator + +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertyReservationsQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.SandboxPropertyReservationsTotalCountQuery +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.getSandboxReservationsOperation + +/** + * Represents a paginated response for [SandboxPropertyReservationsQuery] GraphQL operation, containing a list + * of sandbox reservation data, pagination details, and the full raw GraphQL response. + * + * @param data A list of [SandboxReservationData] representing the sandbox reservations in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class SandboxReservationsPaginatedResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, SandboxPropertyReservationsQuery.Data> + +/** + * Provides an iterator to retrieve sandbox reservations in a paginated manner using the [SandboxPropertyReservationsQuery] + * GraphQL operation, allowing seamless iteration over pages of reservations for a specified property. + * + * This paginator uses the specified [GraphQLExecutor] to fetch pages based on a cursor and optional page size, + * managing the pagination state automatically. + * + * @param graphQLExecutor The [GraphQLExecutor] used to execute GraphQL queries. + * @param propertyId The unique identifier of the property for which reservations are being retrieved. + * @param pageSize The number of reservations to retrieve per page; defaults to `null` to use the server's default page size. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @constructor Creates a [SandboxReservationsPaginator] with the specified executor, property ID, page size, and initial cursor. + */ +class SandboxReservationsPaginator @JvmOverloads constructor( + private val graphQLExecutor: GraphQLExecutor, + private val propertyId: String, + private val pageSize: Int? = null, + initialCursor: String? = null +) : Iterator { + private var cursor = initialCursor + private var hasNext: Boolean = true + private var initialized: Boolean = false + + /** + * Checks if there are more pages to fetch. + * + * This method returns `true` if additional pages are available; otherwise, it returns `false`. + * It initializes the paginator by checking if there are reservations to fetch when called for the first time. + * + * @return `true` if there are more pages to fetch, `false` otherwise. + */ + override fun hasNext(): Boolean { + if (!initialized) { + initialized = true + return hasReservationsToFetch() + } + + return hasNext + } + + /** + * Retrieves the next page of sandbox reservations. + * + * This method executes a "Get Sandbox Reservations" query to fetch the next page of reservations, + * updating the pagination state and cursor for subsequent requests. + * + * @return A [SandboxReservationsPaginatedResponse] containing the sandbox reservations, raw response, and pagination details. + * @throws NoSuchElementException If no more pages are available to fetch. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ + override fun next(): SandboxReservationsPaginatedResponse { + if (!hasNext()) { + throw NoSuchElementException("No more pages to fetch") + } + + val response = getSandboxReservationsOperation( + graphQLExecutor = graphQLExecutor, + propertyId = propertyId, + cursor = cursor, + pageSize = pageSize + ) + + cursor = response.pageInfo.nextPageCursor + hasNext = response.pageInfo.hasNext + + return SandboxReservationsPaginatedResponse( + data = response.data, + pageInfo = response.pageInfo, + rawResponse = response.rawResponse + ) + } + + private fun hasReservationsToFetch(): Boolean = run { + graphQLExecutor.execute( + SandboxPropertyReservationsTotalCountQuery(propertyId) + ).let { + it.data.property.reservations.totalCount > 0 + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt new file mode 100644 index 00000000..b44d8cf8 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation + +import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor +import com.expediagroup.sdk.graphql.common.GraphQLClient +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment +import com.expediagroup.sdk.lodgingconnectivity.configuration.SupplyApiEndpointProvider +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationReconciliationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelVrboReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ChangeReservationReconciliationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ConfirmReservationNotificationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.PropertyReservationsInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.RefundReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.CancelReservationReconciliationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.CancelReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.CancelVrboReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.ChangeReservationReconciliationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.ConfirmReservationNotificationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.RefundReservationResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.cancelReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.cancelReservationReconciliationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.cancelVrboReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.changeReservationReconciliationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.confirmReservationNotificationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.refundReservationOperation +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.paginator.ReservationsPaginator +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream.ReservationsStream + +/** + * A client for interacting with EG Lodging Connectivity Reservations GraphQL API + * + * Endpoint is automatically determined based on the environment configuration (e.g., [ClientEnvironment.PROD] (default) or [ClientEnvironment.TEST]) + * + * In addition, this client can be configured to target the sandbox environment by passing[ClientEnvironment.SANDBOX_PROD] or + * [ClientEnvironment.SANDBOX_TEST] to the `environment` configuration option. + * + * @constructor Creates a new instance of `ReservationClient` using the provided configuration. + * @param config The `ClientConfiguration` that includes API credentials and other optional parameters such as environment and + * timeouts + */ +class ReservationClient(config: ClientConfiguration) : GraphQLClient() { + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( + config.toFullClientConfiguration( + apiEndpoint = SupplyApiEndpointProvider.forEnvironment( + environment = config.environment ?: ClientEnvironment.PROD + ), + ) + ) + + /** + * Creates a paginator for retrieving paginated reservation data for a specified property. + * + * @param propertyId The unique identifier of the property. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @param pageSize The number of reservations to retrieve per page; defaults to server’s setting if not provided. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to the first page. + * @return A [ReservationsPaginator] to fetch reservations page by page. + */ + @JvmOverloads + fun getReservationsPaginator( + propertyId: String, + selections: ReservationSelections? = null, + pageSize: Int? = null, + initialCursor: String? = null + ) = run { + ReservationsPaginator( + graphQLExecutor = graphQLExecutor, + input = PropertyReservationsInput(propertyId), + selections = selections, + pageSize = pageSize, + initialCursor = initialCursor + ) + } + + /** + * Creates a paginator for retrieving paginated reservation data for a specified property. + * + * @param propertyId The unique identifier of the property. + * @param pageSize The number of reservations to retrieve per page; defaults to server’s setting if not provided. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to the first page. + * @return A [ReservationsPaginator] to fetch reservations page by page. + */ + @JvmOverloads + fun getReservationsPaginator( + propertyId: String, + pageSize: Int, + initialCursor: String? = null + ) = run { + ReservationsPaginator( + graphQLExecutor = graphQLExecutor, + input = PropertyReservationsInput(propertyId), + pageSize = pageSize, + initialCursor = initialCursor + ) + } + + /** + * Creates a paginator for retrieving paginated reservation data for a specified property. + * + * @param input The [PropertyReservationsInput] specifying propertyId with additional filtering options. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @param pageSize The number of reservations to retrieve per page; defaults to server’s setting if not provided. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to the first page. + * @return A [ReservationsPaginator] to fetch reservations page by page. + */ + @JvmOverloads + fun getReservationsPaginator( + input: PropertyReservationsInput, + selections: ReservationSelections? = null, + pageSize: Int? = null, + initialCursor: String? = null + ) = run { + ReservationsPaginator( + graphQLExecutor = graphQLExecutor, + input = input, + selections = selections, + pageSize = pageSize, + initialCursor = initialCursor + ) + } + + /** + * Creates a paginator for retrieving paginated reservation data for a specified property. + * + * @param input The [PropertyReservationsInput] specifying propertyId with additional filtering options. + * @param pageSize The number of reservations to retrieve per page; defaults to server’s setting if not provided. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to the first page. + * @return A [ReservationsPaginator] to fetch reservations page by page. + */ + @JvmOverloads + fun getReservationsPaginator( + input: PropertyReservationsInput, + pageSize: Int, + initialCursor: String? = null + ) = run { + ReservationsPaginator( + graphQLExecutor = graphQLExecutor, + input = input, + pageSize = pageSize, + initialCursor = initialCursor + ) + } + + /** + * Provides a streaming interface for sequentially accessing reservations for a specific property. + * + * @param propertyId The unique identifier of the property. + * @return A [ReservationsStream] to stream through reservations one at a time. + */ + fun getReservationsStream(propertyId: String) = run { + ReservationsStream(getReservationsPaginator(propertyId)) + } + + /** + * Provides a streaming interface for sequentially accessing reservations for a specific property. + * + * @param input The [PropertyReservationsInput] specifying the property and filter criteria. + * @return A [ReservationsStream] to stream through reservations one at a time. + */ + fun getReservationsStream(input: PropertyReservationsInput) = run { + ReservationsStream(getReservationsPaginator(input)) + } + + /** + * Cancels a reservation. + * + * @param input The [CancelReservationInput] containing the details of the reservation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [CancelReservationResponse] containing the canceled reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun cancelReservation( + input: CancelReservationInput, + selections: ReservationSelections? = null + ) = run { + cancelReservationOperation(graphQLExecutor, input, selections) + } + + /** + * Cancels the reconciliation of a reservation. + * + * @param input The [CancelReservationReconciliationInput] containing the details of the reservation reconciliation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [CancelReservationReconciliationResponse] containing the canceled reconciliation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun cancelReservationReconciliation( + input: CancelReservationReconciliationInput, + selections: ReservationSelections? = null + ) = run { + cancelReservationReconciliationOperation(graphQLExecutor, input, selections) + } + + /** + * Cancels a VRBO reservation. + * + * @param input The [CancelVrboReservationInput] containing the details of the VRBO reservation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [CancelVrboReservationResponse] containing the canceled reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun cancelVrboReservation( + input: CancelVrboReservationInput, + selections: ReservationSelections? = null + ) = run { + cancelVrboReservationOperation(graphQLExecutor, input, selections) + } + + /** + * Updates the reconciliation details for a reservation. + * + * @param input The [ChangeReservationReconciliationInput] containing the new reconciliation details for the reservation. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [ChangeReservationReconciliationResponse] containing the updated reconciliation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun changeReservationReconciliation( + input: ChangeReservationReconciliationInput, + selections: ReservationSelections? = null + ) = run { + changeReservationReconciliationOperation(graphQLExecutor, input, selections) + } + + /** + * Confirms a reservation notification. + * + * @param input The [ConfirmReservationNotificationInput] containing the details of the reservation notification to confirm. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [ConfirmReservationNotificationResponse] containing the confirmed reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun confirmReservationNotification( + input: ConfirmReservationNotificationInput, + selections: ReservationSelections? = null + ) = run { + confirmReservationNotificationOperation(graphQLExecutor, input, selections) + } + + /** + * Initiates a refund for a reservation. + * + * @param input The [RefundReservationInput] containing the details of the reservation to be refunded. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response. + * @return A [RefundReservationResponse] containing the refunded reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the operation execution. + */ + @JvmOverloads + fun refundReservation( + input: RefundReservationInput, + selections: ReservationSelections? = null + ) = run { + refundReservationOperation(graphQLExecutor, input, selections) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/constant/Constant.kt similarity index 54% rename from code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/constant/Constant.kt index 5a09ffcf..c40f3a9a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/graphql/GraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/constant/Constant.kt @@ -14,15 +14,8 @@ * limitations under the License. */ -package com.expediagroup.sdk.lodgingconnectivity.graphql +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.constant -import com.apollographql.apollo.api.Mutation -import com.apollographql.apollo.api.Query -import java.util.concurrent.CompletableFuture - -interface GraphQLExecutor { - fun executeAsync(query: Query): CompletableFuture - fun executeAsync(mutation: Mutation): CompletableFuture - fun execute(query: Query): T - fun execute(mutation: Mutation): T +internal object Constant { + const val RESERVATIONS_DEFAULT_PAGE_SIZE: Int = 25 } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationOperation.kt new file mode 100644 index 00000000..803204ef --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.CancelReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [CancelReservationMutation] GraphQL operation, containing both the processed + * reservation data after cancellation (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the canceled reservation, + * or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class CancelReservationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [CancelReservationMutation] GraphQL mutation to cancel an existing reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CancelReservationResponse] + * containing both the canceled reservation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CancelReservationInput] containing the details of the reservation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [CancelReservationResponse] containing the canceled reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun cancelReservationOperation( + graphQLExecutor: GraphQLExecutor, + input: CancelReservationInput, + selections: ReservationSelections? = null +): CancelReservationResponse { + val operation = CancelReservationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return CancelReservationResponse( + data = response.data.cancelReservation.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationReconciliationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationReconciliationOperation.kt new file mode 100644 index 00000000..75020e66 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelReservationReconciliationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.CancelReservationReconciliationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelReservationReconciliationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [CancelReservationReconciliationMutation] GraphQL operation, containing both the processed + * reservation data after reconciliation cancellation (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the reservation + * after the reconciliation cancellation, or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class CancelReservationReconciliationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [CancelReservationReconciliationMutation] GraphQL mutation to cancel the reconciliation of an existing reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CancelReservationReconciliationResponse] + * containing both the canceled reconciliation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CancelReservationReconciliationInput] containing the details of the reservation reconciliation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [CancelReservationReconciliationResponse] containing the canceled reconciliation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun cancelReservationReconciliationOperation( + graphQLExecutor: GraphQLExecutor, + input: CancelReservationReconciliationInput, + selections: ReservationSelections? = null +): CancelReservationReconciliationResponse { + val operation = CancelReservationReconciliationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return CancelReservationReconciliationResponse( + data = response.data.cancelReservationReconciliation.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelVrboReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelVrboReservationOperation.kt new file mode 100644 index 00000000..09a9f3aa --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/CancelVrboReservationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.CancelVrboReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.CancelVrboReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [CancelVrboReservationMutation] GraphQL operation, containing both the processed + * reservation data after cancellation (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the VRBO reservation + * after cancellation, or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class CancelVrboReservationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [CancelVrboReservationMutation] GraphQL mutation to cancel an existing VRBO reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [CancelVrboReservationResponse] + * containing both the canceled reservation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [CancelVrboReservationInput] containing the details of the VRBO reservation to be canceled. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [CancelVrboReservationResponse] containing the canceled reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun cancelVrboReservationOperation( + graphQLExecutor: GraphQLExecutor, + input: CancelVrboReservationInput, + selections: ReservationSelections? = null +): CancelVrboReservationResponse { + val operation = CancelVrboReservationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return CancelVrboReservationResponse( + data = response.data.cancelVrboReservation.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ChangeReservationReconciliationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ChangeReservationReconciliationOperation.kt new file mode 100644 index 00000000..cb979f31 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ChangeReservationReconciliationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.ChangeReservationReconciliationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ChangeReservationReconciliationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [ChangeReservationReconciliationMutation] GraphQL operation, containing both the processed + * reservation data after reconciliation changes (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the reservation + * after the reconciliation changes, or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class ChangeReservationReconciliationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [ChangeReservationReconciliationMutation] GraphQL mutation to modify the reconciliation details of an existing reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [ChangeReservationReconciliationResponse] + * containing both the updated reconciliation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [ChangeReservationReconciliationInput] containing the details of the reconciliation changes. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [ChangeReservationReconciliationResponse] containing the updated reconciliation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun changeReservationReconciliationOperation( + graphQLExecutor: GraphQLExecutor, + input: ChangeReservationReconciliationInput, + selections: ReservationSelections? = null +): ChangeReservationReconciliationResponse { + val operation = ChangeReservationReconciliationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return ChangeReservationReconciliationResponse( + data = response.data.changeReservationReconciliation.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ConfirmReservationNotificationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ConfirmReservationNotificationOperation.kt new file mode 100644 index 00000000..d8ea5e77 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/ConfirmReservationNotificationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.ConfirmReservationNotificationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ConfirmReservationNotificationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [ConfirmReservationNotificationMutation] GraphQL operation, containing both the processed + * reservation data after confirmation (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the confirmed reservation, + * or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class ConfirmReservationNotificationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [ConfirmReservationNotificationMutation] GraphQL mutation to confirm the notification for an existing reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [ConfirmReservationNotificationResponse] + * containing both the confirmed reservation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [ConfirmReservationNotificationInput] containing the details of the reservation notification to confirm. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [ConfirmReservationNotificationResponse] containing the confirmed reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun confirmReservationNotificationOperation( + graphQLExecutor: GraphQLExecutor, + input: ConfirmReservationNotificationInput, + selections: ReservationSelections? = null +): ConfirmReservationNotificationResponse { + val operation = ConfirmReservationNotificationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return ConfirmReservationNotificationResponse( + data = response.data.confirmReservationNotification.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/GetReservationsOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/GetReservationsOperation.kt new file mode 100644 index 00000000..9502aa10 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/GetReservationsOperation.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.getOrThrow +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.core.extension.orNullIfBlank +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.PropertyReservationsQuery +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.PropertyReservationsInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.constant.Constant + +/** + * Represents the paginated response for [PropertyReservationsQuery] GraphQL operation, containing a list + * of reservation data, pagination information, and the full raw GraphQL response. + * + * @param data A list of nullable [ReservationData?] representing the reservations for the specified property in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class PropertyReservationsResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, PropertyReservationsQuery.Data> + +/** + * Executes [PropertyReservationsQuery] GraphQL query to retrieve a paginated list of reservations for a specific property. + * + * This function uses the provided [GraphQLExecutor] to execute the query, with optional parameters for cursor, + * page size, and selection of additional reservation details. It returns a [PropertyReservationsResponse] + * containing the reservation data, pagination information, and the full raw response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL query. + * @param input The [PropertyReservationsInput] containing the property ID and filter criteria for the reservations. + * @param cursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @param pageSize The number of reservations to retrieve per page; defaults to a predefined page size if not provided. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, + * such as supplier amount and payment instrument token; defaults to `null`. + * @return A [PropertyReservationsResponse] containing the reservation data, pagination information, and the full raw response. + * @throws ExpediaGroupServiceException If the property data is not available in the response. + */ +@JvmOverloads +fun getReservationsOperation( + graphQLExecutor: GraphQLExecutor, + input: PropertyReservationsInput, + cursor: String? = null, + pageSize: Int? = null, + selections: ReservationSelections? = null +): PropertyReservationsResponse { + val operation = PropertyReservationsQuery + .builder() + .propertyId(input.propertyId) + .idSource(input.idSource.getOrNull()) + .pageSize(pageSize ?: Constant.RESERVATIONS_DEFAULT_PAGE_SIZE) + .cursor(cursor) + .filter(input.filter.getOrNull()) + .checkOutDate(input.checkOutDate.getOrNull()) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + val property = response.data.property.getOrThrow { + ExpediaGroupServiceException("Failed to fetch property ${input.propertyId}") + } + + val reservationsPage = property.reservations + + val nextPageInfo = reservationsPage.pageInfo + + val currentPageInfo = PageInfo( + cursor = cursor, + nextPageCursor = nextPageInfo?.endCursor?.orNullIfBlank(), + hasNext = nextPageInfo?.hasNextPage ?: false, + pageSize = reservationsPage.edges.size, + totalCount = reservationsPage.totalCount + ) + + return PropertyReservationsResponse( + data = reservationsPage.edges.map { edgeOptional -> edgeOptional?.node?.reservationData }, + rawResponse = response, + pageInfo = currentPageInfo + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/RefundReservationOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/RefundReservationOperation.kt new file mode 100644 index 00000000..29738807 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/operation/RefundReservationOperation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation + +import com.expediagroup.sdk.core.extension.orFalseIfNull +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.graphql.model.response.Response +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.RefundReservationMutation +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.RefundReservationInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections + +/** + * Represents the response for [RefundReservationMutation] GraphQL operation, containing both the processed + * reservation data after the refund (if available) and the full raw GraphQL response. + * + * @param data The [ReservationData] extracted from the raw response, representing details of the reservation + * after the refund process, or `null` if no reservation data is returned. + * @param rawResponse The raw response from the GraphQL mutation, including the complete data structure and any associated errors. + */ +data class RefundReservationResponse( + override val data: ReservationData?, + override val rawResponse: RawResponse, +) : Response + +/** + * Executes [RefundReservationMutation] GraphQL mutation to initiate a refund on an existing reservation. + * + * This function uses the provided [GraphQLExecutor] to execute the mutation and returns a [RefundReservationResponse] + * containing both the refunded reservation data (if available) and the full raw response. Optional selection parameters + * allow the inclusion of additional reservation details in the response. + * + * @param graphQLExecutor The [GraphQLExecutor] responsible for executing the GraphQL mutation. + * @param input The [RefundReservationInput] containing the details of the reservation to be refunded. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @return A [RefundReservationResponse] containing the refunded reservation data (if available) and the full raw response. + * @throws ExpediaGroupServiceException If an error occurs during the mutation execution. + */ +@JvmOverloads +fun refundReservationOperation( + graphQLExecutor: GraphQLExecutor, + input: RefundReservationInput, + selections: ReservationSelections? = null +): RefundReservationResponse { + val operation = RefundReservationMutation + .builder() + .input(input) + .includeSupplierAmount(selections?.includeSupplierAmount.orFalseIfNull()) + .includePaymentInstrumentToken(selections?.includePaymentInstrumentToken.orFalseIfNull()) + .build() + + val response = graphQLExecutor.execute(operation) + + return RefundReservationResponse( + data = response.data.refundReservation.reservation?.reservationData, + rawResponse = response + ) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/paginator/ReservationsPaginator.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/paginator/ReservationsPaginator.kt new file mode 100644 index 00000000..e2e2c4b7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/paginator/ReservationsPaginator.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.paginator + +import com.expediagroup.sdk.core.extension.getOrThrow +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.graphql.model.paging.PageInfo +import com.expediagroup.sdk.graphql.model.response.PaginatedResponse +import com.expediagroup.sdk.graphql.model.response.RawResponse +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.PropertyReservationsQuery +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.PropertyReservationsTotalCountQuery +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.PropertyReservationsInput +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.type.ReservationSelections +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.constant.Constant +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.operation.getReservationsOperation + +/** + * Represents a paginated response for [PropertyReservationsQuery] GraphQL operation, containing a list + * of reservation data, pagination information, and the full raw GraphQL response. + * + * @param data A list of nullable [ReservationData] representing the reservations for the specified property in the current page. + * @param rawResponse The raw response from the GraphQL query, including the complete data structure and any associated errors. + * @param pageInfo Metadata about the pagination state, including the current cursor, the cursor for the next page, + * the page size, and whether more pages are available. + */ +data class ReservationsPaginatedResponse( + override val data: List, + override val rawResponse: RawResponse, + override val pageInfo: PageInfo +) : PaginatedResponse, PropertyReservationsQuery.Data> + + +/** + * Provides an iterator to retrieve property reservations in a paginated manner using [PropertyReservationsQuery] + * GraphQL operation, allowing seamless iteration over pages of reservations for a specified property. + * + * This paginator uses the specified [GraphQLExecutor] to fetch pages based on cursor, page size, and optional reservation + * field selections, managing the pagination state automatically. + * + * @param graphQLExecutor The [GraphQLExecutor] used to execute GraphQL queries. + * @param input The [PropertyReservationsInput] specifying the property ID and filter criteria for retrieving reservations. + * @param selections An optional [ReservationSelections] specifying additional fields to include in the response, such as + * supplier amount and payment instrument token; defaults to `null`. + * @param pageSize The number of reservations to retrieve per page; defaults to `null` to use the server's default page size. + * @param initialCursor An optional cursor to specify the starting point for pagination; defaults to `null` for the first page. + * @constructor Creates a [ReservationsPaginator] with the specified executor, input parameters, and initial cursor. + */ +class ReservationsPaginator @JvmOverloads constructor( + private val graphQLExecutor: GraphQLExecutor, + private val input: PropertyReservationsInput, + private val selections: ReservationSelections? = null, + private val pageSize: Int? = null, + initialCursor: String? = null +) : Iterator { + private var cursor: String? = initialCursor + private var hasNext: Boolean = true + private var initialized: Boolean = false + + /** + * Checks if there are more pages to fetch. + * + * This method returns `true` if additional pages are available; otherwise, it returns `false`. + * It initializes the paginator by checking if there are reservations to fetch when called for the first time. + * + * @return `true` if there are more pages to fetch, `false` otherwise. + */ + override fun hasNext(): Boolean { + if (!initialized) { + initialized = true + return hasReservationsToFetch() + } + + return hasNext + } + + /** + * Retrieves the next page of property reservations. + * + * This method executes [PropertyReservationsQuery] query to fetch the next page of reservations, + * updating the pagination state and cursor for subsequent requests. + * + * @return A [ReservationsPaginatedResponse] containing the property reservations, raw response, and pagination details. + * @throws NoSuchElementException If no more pages are available to fetch. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ + override fun next(): ReservationsPaginatedResponse { + if (!hasNext()) { + throw NoSuchElementException("No more pages to fetch") + } + + val response = getReservationsOperation( + graphQLExecutor = graphQLExecutor, + input = input, + selections = selections, + cursor = cursor, + pageSize = pageSize + ) + + cursor = response.pageInfo.nextPageCursor + hasNext = response.pageInfo.hasNext + + return ReservationsPaginatedResponse( + data = response.data, + pageInfo = response.pageInfo, + rawResponse = response.rawResponse, + ) + } + + /** + * Checks if there are any reservations available to fetch, initializing the paginator if necessary. + * + * @return `true` if there are reservations available, `false` otherwise. + * @throws ExpediaGroupServiceException If an error occurs during the query execution. + */ + private fun hasReservationsToFetch(): Boolean = run { + graphQLExecutor.execute( + PropertyReservationsTotalCountQuery + .builder() + .propertyId(input.propertyId) + .idSource(input.idSource.getOrNull()) + .pageSize(pageSize ?: Constant.RESERVATIONS_DEFAULT_PAGE_SIZE) + .cursor(cursor) + .build() + ).let { + it.data.property.getOrThrow { + ExpediaGroupServiceException("Failed to fetch property ${input.propertyId}") + } + }.let { + val totalCount = it.reservations.totalCount ?: 0 + totalCount > 0 + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/stream/ReservationsStream.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/stream/ReservationsStream.kt new file mode 100644 index 00000000..1dd30177 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/stream/ReservationsStream.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream + +import com.expediagroup.sdk.graphql.model.paging.PaginatedStream +import com.expediagroup.sdk.lodgingconnectivity.supply.operation.fragment.ReservationData +import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.paginator.ReservationsPaginator + +/** + * Provides a streaming interface for sequentially accessing reservations through a paginated stream. + * + * The `ReservationsStream` uses a [ReservationsPaginator] to fetch reservations page by page, allowing + * items to be retrieved one at a time without manually handling pagination logic. + * + * @param paginator The [ReservationsPaginator] responsible for retrieving reservations in a paginated format. + */ +class ReservationsStream( + private val paginator: ReservationsPaginator +) : PaginatedStream() { + + /** + * Retrieves the next reservation item from the stream. + * + * This method fetches the next page of reservations from the paginator if the current page is exhausted. + * Returns `null` if there are no more items to retrieve. + * + * @return The next [ReservationData] item in the stream, or `null` if no more items are available. + */ + override fun nextItem(): ReservationData? { + if (isCurrentPageEmpty()) { + if (!paginator.hasNext()) { + return null + } + + fetchNextPage { + paginator.next().data + } + } + return pollCurrentPage() + } +} diff --git a/code/tasks-gradle/apollo.gradle b/code/tasks-gradle/apollo.gradle index e0639359..f507bd2b 100644 --- a/code/tasks-gradle/apollo.gradle +++ b/code/tasks-gradle/apollo.gradle @@ -1,17 +1,16 @@ apollo { service("supply") { - generateKotlinModels.set(false) - nullableFieldStyle.set("javaOptional") - generateOptionalOperationVariables.set(false) - + /* Schema & operations files configurations */ srcDir("src/main/graphql/supply") - + schemaFiles.from('src/main/graphql/supply/schema.graphqls', 'src/main/graphql/supply/reservations/extension/schema-extensions.graphqls') excludes.add("experimental/**/*.graphql") + packageName.set("com.expediagroup.sdk.lodgingconnectivity.supply.operation") - packageName.set("com.expediagroup.sdk.lodgingconnectivity.graphql.supply") + /* Generated models configurations */ + generateInputBuilders.set(true) + generateMethods.set(List.of("equalsHashCode", "toString")) /**** Custom Scalars Mappings ****/ - // Deserialized as Strings without explicit adapters mapScalar("JSON", "java.lang.String") mapScalar("Decimal", "java.lang.String") @@ -21,43 +20,48 @@ apollo { mapScalar("CountryCode", "java.lang.String") // Mapped Using Apollo Provided Adapters - mapScalar("LocalDateTime", "java.time.LocalDateTime", "com.apollographql.adapter.core.JavaLocalDateTimeAdapter.INSTANCE") - mapScalar("LocalDate", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter.INSTANCE") - mapScalar("LocalTime", "java.time.LocalTime", "com.apollographql.adapter.core.JavaLocalTimeAdapter.INSTANCE") - mapScalar("Date", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter.INSTANCE") + mapScalar("LocalDateTime", "java.time.LocalDateTime", "com.apollographql.adapter.core.JavaLocalDateTimeAdapter") + mapScalar("LocalDate", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter") + mapScalar("LocalTime", "java.time.LocalTime", "com.apollographql.adapter.core.JavaLocalTimeAdapter") + mapScalar("Date", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter") + mapScalar("DateTime", "java.time.OffsetDateTime", "com.apollographql.adapter.core.JavaOffsetDateTimeAdapter") + mapScalar("ZoneDateTime", "java.time.ZonedDateTime", "com.apollographql.adapter.core.JavaZonedDateTimeAdapter") // Mapped Using Custom Adapters - mapScalar("Url", "java.net.URL", "com.expediagroup.sdk.lodgingconnectivity.graphql.adapter.URLAdapter.INSTANCE") - mapScalar("ZoneDateTime", "java.time.ZonedDateTime", "com.expediagroup.sdk.lodgingconnectivity.graphql.adapter.ZoneDateTimeAdapter.INSTANCE") - mapScalar("DateTime", "java.time.OffsetDateTime", "com.expediagroup.sdk.lodgingconnectivity.graphql.adapter.DateTimeAdapter.INSTANCE") + mapScalar("Url", "java.net.URL", "com.expediagroup.sdk.lodgingconnectivity.graphql.adapter.URLAdapter()") + + plugin(project(":apollo-compiler-plugin")) } service("payment") { - generateKotlinModels.set(false) - nullableFieldStyle.set("javaOptional") - generateOptionalOperationVariables.set(false) - + /* Schema & operations files configurations */ srcDir("src/main/graphql/payment") - excludes.add("experimental/**/*.graphql") + packageName.set("com.expediagroup.sdk.lodgingconnectivity.payment.operation") - packageName.set("com.expediagroup.sdk.lodgingconnectivity.graphql.payment") + /* Generated models configurations */ + generateInputBuilders.set(true) + generateMethods.set(List.of("equalsHashCode", "toString")) /**** Custom Scalars Mappings ****/ mapScalar("CountryCode", "java.lang.String") - mapScalar("DateTime", "java.time.OffsetDateTime", "com.expediagroup.sdk.lodgingconnectivity.graphql.adapter.DateTimeAdapter.INSTANCE") + mapScalar("DateTime", "java.time.OffsetDateTime", "com.apollographql.adapter.core.JavaOffsetDateTimeAdapter") + + plugin(project(":apollo-compiler-plugin")) } service("sandbox") { - generateKotlinModels.set(false) - nullableFieldStyle.set("javaOptional") - generateOptionalOperationVariables.set(false) - + /* Schema & operations files configurations */ srcDir("src/main/graphql/sandbox") - + schemaFiles.from('src/main/graphql/sandbox/schema.graphqls', 'src/main/graphql/sandbox/reservations/extension/schema-extensions.graphqls') excludes.add("experimental/**/*.graphql") + packageName.set("com.expediagroup.sdk.lodgingconnectivity.sandbox.operation") - packageName.set("com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox") + /* Generated models configurations */ + generateInputBuilders.set(true) + generateMethods.set(List.of("equalsHashCode", "toString")) /**** Custom Scalars Mappings ****/ - mapScalar("Date", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter.INSTANCE") + mapScalar("Date", "java.time.LocalDate", "com.apollographql.adapter.core.JavaLocalDateAdapter") + + plugin(project(":apollo-compiler-plugin")) } } diff --git a/docs/payment-client.md b/docs/payment-client.md index 66018cf2..c65ba20d 100644 --- a/docs/payment-client.md +++ b/docs/payment-client.md @@ -76,7 +76,7 @@ At the moment, there is only one query called `PaymentInstrument` you can execut **Resources** - ⚠️ Documentation is unavailable at the moment -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/payment/operations/mutations/PaymentInstrument.query.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/payment/queries/PaymentInstrument.graphql) - [Reference]() diff --git a/docs/supply-client.md b/docs/reservation-client.md similarity index 80% rename from docs/supply-client.md rename to docs/reservation-client.md index db1304ec..75a68258 100644 --- a/docs/supply-client.md +++ b/docs/reservation-client.md @@ -1,9 +1,5 @@ -# Supply Client -`SupplyClient` gives you access to the reservations capabilities in lodging connectivity APIs. - -> [!NOTE] -> Supporting more capabilities like Promotions, Messaging, Reviews, etc... is work-in-progress at the moment and will be added soon to this client. - +# Reservation Client +`ReservationnClient` gives you access to the reservations capabilities in lodging connectivity APIs. ### API Endpoint This client is connected with https://api.expediagroup.com/supply/lodging/graphql endpoint by default. @@ -18,11 +14,11 @@ ClientConfiguration config = ClientConfiguration .secret("SECRET") .build(); -SupplyClient supplyClient = new SupplyClient(config); +ReservationClient reservationClient = new ReservationClient(config); ``` ### Set the Environment (Optional) -`SupplyClient` can be configured to work in different environments, below is a list of the supported environments by this client: +`ReservationClient` can be configured to work in different environments, below is a list of the supported environments by this client: | Environment | Corresponding API Endpoint | |----------------------------------|------------------------------------------------------------------| @@ -43,7 +39,8 @@ ClientConfiguration config = ClientConfiguration ### Initialize GraphQL Operation ```java -PropertyReservationsQuery reservationsQuery = PropertyReservationsQuery + +PropertyReservationsQuery propertyReservationsQuery = PropertyReservationsQuery .builder() .propertyId("your_property_id") .pageSize(10) @@ -53,7 +50,7 @@ PropertyReservationsQuery reservationsQuery = PropertyReservationsQuery ### Execute the operation ```java try { - PropertyReservationsQuery.Data reservationsData = supplyClient.execute(reservationsQuery); + PropertyReservationsQuery.Data reservationsData = reservationClient.execute(propertyReservationsQuery); } catch(ExpediaGroupServiceException e) { e.printStackTrace(); @@ -61,11 +58,11 @@ catch(ExpediaGroupServiceException e) { ``` ## Available Operations -The SDK offers a set of queries & mutations you can execute using the `SupplyClient`. Below is an overview list of the available operations. +The SDK offers a set of queries & mutations you can execute using the `ReservationClient`. Below is an overview list of the available operations.
-### SupplyClient - Queries +### ReservationClient - Queries
@@ -82,7 +79,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli |---------------------------------|--------------------------|-----------------------| | `propertyId` | `String!` | Yes | | `idSource` | `IdSource` | No (default: EXPEDIA) | -| `pageSize` | `Int!` | Yes | +| `pageSize` | `Int!` | No (default: 10) | | `cursor` | `String` | No | | `filter` | `ReservationFilterInput` | No | | `checkOutDate` | `CheckOutDateFilter` | No | @@ -93,7 +90,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - [Documentation](https://developers.expediagroup.com/supply/lodging/docs/booking_apis/reservations/reference/reservations_query/) -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/queries/PropertyReservations.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/queries/PropertyReservations.graphql) - [Reference]() @@ -113,7 +110,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli |----------------|--------------------------|-----------------------| | `propertyId` | `String!` | Yes | | `idSource` | `IdSource` | No (default: EXPEDIA) | -| `pageSize` | `Int!` | Yes | +| `pageSize` | `Int!` | No (default: 10) | | `cursor` | `String` | No | | `filter` | `ReservationFilterInput` | No | | `checkOutDate` | `CheckOutDateFilter` | No | @@ -122,7 +119,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - [Documentation](https://developers.expediagroup.com/supply/lodging/docs/booking_apis/reservations/reference/reservations_query/) -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/queries/PropertyReservationsSummary.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/queries/PropertyReservationsSummary.graphql) - [Reference]() @@ -131,7 +128,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli

-### SupplyClient - Mutations +### ReservationClient - Mutations
@@ -153,7 +150,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - [Documentation](https://developers.expediagroup.com/supply/lodging/docs/booking_apis/reservations/reference/cancelReservation/) -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/CancelReservation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/CancelReservation.graphql) - [Reference]() @@ -177,7 +174,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - [Documentation](https://developers.expediagroup.com/supply/lodging/docs/booking_apis/reservations/reference/cancelReservationReconciliation/) -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/CancelReservationReconciliation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/CancelReservationReconciliation.graphql) - [Reference]() @@ -201,7 +198,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/CancelVrboReservation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/CancelVrboReservation.graphql) - [Reference]() @@ -225,7 +222,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - [Documentation](https://developers.expediagroup.com/supply/lodging/docs/booking_apis/reservations/reference/changeReservationReconciliation/) -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/ChangeReservationReconciliation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/ChangeReservationReconciliation.graphql) - [Reference]() @@ -249,7 +246,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/ConfirmReservationNotification.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/ConfirmReservationNotification.graphql) - [Reference]() @@ -273,7 +270,7 @@ The SDK offers a set of queries & mutations you can execute using the `SupplyCli **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/operations/mutations/RefundReservation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/supply/reservations/mutations/RefundReservation.graphql) - [Reference]() diff --git a/docs/sandbox-data-management-client.md b/docs/sandbox-data-management-client.md index 3bb17109..0c650afa 100644 --- a/docs/sandbox-data-management-client.md +++ b/docs/sandbox-data-management-client.md @@ -76,14 +76,14 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa | Name | Type | Required | |--------------------|--------------------|---------------------| | `cursor` | `String` | No | -| `limit` | `Int` | No | -| `skipReservations` | `Boolean! = false` | No (defaults false) | +| `pageSize` | `Int` | No | +| `skipReservations` | `Boolean! = false` | No (default: false) |
**Resources** - ⚠️ Documentation is unavailable at the moment -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/queries/SandboxProperties.query.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/queries/SandboxProperties.graphql) - [Reference]() @@ -102,15 +102,13 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa | Name | Type | Required | |----------------------|--------------------|---------------------| | `id` | `ID!` | Yes | -| `reservationsCursor` | `String` | No | -| `reservationsLimit` | `Int` | No | -| `skipReservations` | `Boolean! = false` | No (defaults false) | +| `skipReservations` | `Boolean! = false` | No (default: false) |
**Resources** - ⚠️ Documentation is unavailable at the moment -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/queries/SandboxProperty.query.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/queries/SandboxProperty.graphql) - [Reference]() @@ -134,7 +132,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/queries/SandboxReservation.query.graphql) +- [Query Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/queries/SandboxReservation.graphql) - [Reference]() @@ -166,7 +164,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxCancelReservation.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxCancelReservation.graphql) - [Reference]() @@ -190,7 +188,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxChangeReservationStayDates.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxChangeReservationStayDates.graphql) - [Reference]() @@ -214,7 +212,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxCreateProperty.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxCreateProperty.graphql) - [Reference]() @@ -238,7 +236,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxCreateReservation.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxCreateReservation.graphql) - [Reference]() @@ -262,7 +260,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxDeleteProperty.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxDeleteProperty.graphql) - [Reference]() @@ -286,7 +284,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxDeletePropertyReservations.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxDeletePropertyReservations.graphql) - [Reference]() @@ -306,11 +304,12 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa |---------|---------------------------|----------| | `input` | `DeleteReservationInput!` | Yes | +
**Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxDeleteReservation.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxDeleteReservation.graphql) - [Reference]() @@ -334,7 +333,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxUpdateProperty.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxUpdateProperty.graphql) - [Reference]() @@ -358,7 +357,7 @@ The SDK offers a set of queries & mutations you can execute using the `SandboxDa **Resources** - ⚠️ Documentation is unavailable at the moment -- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/operations/mutations/SandboxUpdateReservation.mutation.graphql) +- [Mutation Definition](https://github.com/ExpediaGroup/lodging-connectivity-graphql-operations/blob/main/sandbox/reservations/mutations/SandboxUpdateReservation.graphql) - [Reference]() diff --git a/examples/build.gradle b/examples/build.gradle index f03011c6..6f5f3508 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,4 +1,5 @@ dependencies { + implementation 'org.slf4j:slf4j-simple:2.0.16' implementation(project(":code")) } diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java index b58ebed3..ced749ae 100644 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java +++ b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java @@ -16,20 +16,29 @@ package com.expediagroup.sdk.lodgingconnectivity; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.*; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.type.*; import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment; -import com.expediagroup.sdk.lodgingconnectivity.graphql.sandbox.SandboxDataManagementClient; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.SandboxDataManagementClient; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.fragment.SandboxReservationData; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.ChangeReservationStayDatesInput; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreatePropertyInput; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.CreateReservationInput; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdatePropertyInput; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.operation.type.UpdateReservationInput; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.CreateSandboxPropertyResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.DeleteSandboxPropertyResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.GetSandboxPropertiesResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.property.operation.UpdateSandboxPropertyResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.CancelSandboxReservationResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.ChangeSandboxReservationStayDatesResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.CreateSandboxReservationResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.DeleteSandboxReservationResponse; +import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.operation.UpdateSandboxReservationResponse; import java.time.LocalDate; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.ExecutionException; /** - * Example class to demonstrate different operations supported by the LodgingSupplySandboxDataManagementClient + * Example class to demonstrate different operations supported by the SandboxDataManagementClient * Run the main method to see these operations in action: * 1. Create a Property * 2. Update Property Name @@ -40,77 +49,128 @@ * 7. Delete the Reservation * 8. Delete the Property **/ -public class LodgingSupplySandboxDataManagementClientUsageExample { +public class SandboxDataManagementClientUsageExample { private static final SandboxDataManagementClient client = new SandboxDataManagementClient( ClientConfiguration .builder() .key("KEY") .secret("SECRET") - .environment(ClientEnvironment.TEST) .build() ); private static final String PROPERTY_NAME = "Lodging SDK Test Property"; private static final String UPDATED_PROPERTY_NAME = "New Lodging SDK Test Property"; - public static void main(String[] args) throws ExecutionException, InterruptedException { + public static void main(String[] args) { // Delete any old property if it has the same name used in the test run deletePropertyIfExists(); // ******* Create Property ******* - CreatePropertyInput createPropertyInput = CreatePropertyInput.builder().name(Optional.of(PROPERTY_NAME)).build(); - SandboxCreatePropertyMutation.Data createPropertyResponse = client.executeAsync(new SandboxCreatePropertyMutation(createPropertyInput)).get(); + CreatePropertyInput createPropertyInput = CreatePropertyInput + .builder() + .name(PROPERTY_NAME) + .build(); + + CreateSandboxPropertyResponse createPropertyResponse = client.createProperty(createPropertyInput); + + String propertyId = createPropertyResponse.getData().getId(); + System.out.println("Property Created: " + propertyId); + System.out.println(createPropertyResponse); - String propertyId = createPropertyResponse.createProperty.property.id; // ******* Update Property Name ******* - UpdatePropertyInput updatePropertyInput = UpdatePropertyInput.builder().id(propertyId).name(Optional.of(UPDATED_PROPERTY_NAME)).build(); - SandboxUpdatePropertyMutation.Data updatePropertyResponse = client.executeAsync(new SandboxUpdatePropertyMutation(updatePropertyInput)).get(); + UpdatePropertyInput updatePropertyInput = UpdatePropertyInput + .builder() + .id(propertyId) + .name(UPDATED_PROPERTY_NAME) + .build(); + + UpdateSandboxPropertyResponse updatePropertyResponse = client.updateProperty(updatePropertyInput); + + System.out.println("Property Updated: " + propertyId); + System.out.println(updatePropertyResponse); + // ******* Create Reservation ******* - CreateReservationInput createReservationInput = CreateReservationInput.builder().propertyId(propertyId).childCount(Optional.of(4)).adultCount(Optional.of(2)).build(); - SandboxCreateReservationMutation.Data createReservationResponse = client.executeAsync(new SandboxCreateReservationMutation(createReservationInput)).get(); + CreateReservationInput createReservationInput = CreateReservationInput + .builder() + .propertyId(propertyId) + .childCount(4) + .adultCount(2) + .build(); + CreateSandboxReservationResponse createReservationResponse = client.createReservation(createReservationInput); + + SandboxReservationData reservationData = createReservationResponse.getData(); + + System.out.println("Reservation Created: " + reservationData.getId()); + System.out.println(createReservationResponse); - String reservationId = createReservationResponse.createReservation.reservation.sandboxReservationFragment.id; // ******* Update Reservation ******* - UpdateReservationInput updateReservationInput = UpdateReservationInput.builder().id(reservationId).childAges(Optional.of(new ArrayList<>(Arrays.asList(3, 5, 7)))).build(); - SandboxUpdateReservationMutation.Data updateReservationResponse = client.executeAsync(new SandboxUpdateReservationMutation(updateReservationInput)).get(); + UpdateReservationInput updateReservationInput = UpdateReservationInput + .builder() + .id(reservationData.getId()) + .childAges(Arrays.asList(3, 5, 7)) + .build(); + + UpdateSandboxReservationResponse updateReservationResponse = client.updateReservation(updateReservationInput); + + reservationData = updateReservationResponse.getData(); + + System.out.println("Reservation Updated: " + reservationData.getId()); + System.out.println(updateReservationResponse); + // ******* Update Reservation Stay Dates ******* - ChangeReservationStayDatesInput changeStayDatesInput = ChangeReservationStayDatesInput + ChangeReservationStayDatesInput changeReservationStayDatesInput = ChangeReservationStayDatesInput .builder() - .id(reservationId) + .id(reservationData.getId()) .checkInDate(LocalDate.of(2024, 6, 5)) .checkOutDate(LocalDate.of(2024, 6, 10)) .build(); - SandboxChangeReservationStayDatesMutation.Data changeStayDatesResponse = client.executeAsync(new SandboxChangeReservationStayDatesMutation(changeStayDatesInput)).get(); + ChangeSandboxReservationStayDatesResponse changeStayDatesResponse = client.changeReservationStayDates(changeReservationStayDatesInput); + + reservationData = changeStayDatesResponse.getData(); + + System.out.println("Reservation Stay Dates Updated: " + reservationData.getId()); + System.out.println(changeStayDatesResponse); // ******* Cancel Reservation ******* - CancelReservationInput cancelReservationInput = CancelReservationInput.builder().id(reservationId).sendNotification(Optional.of(false)).build(); - SandboxCancelReservationMutation.Data cancelReservationResponse = client.executeAsync(new SandboxCancelReservationMutation(cancelReservationInput)).get(); + CancelSandboxReservationResponse cancelReservationResponse = client.cancelReservation(reservationData.getId()); + + reservationData = cancelReservationResponse.getData(); + + System.out.println("Reservation Was Canceled: " + reservationData.getId()); + System.out.println(cancelReservationResponse); + // ******* Delete Reservation ******* - DeleteReservationInput deleteReservationInput = DeleteReservationInput.builder().id(reservationId).build(); - SandboxDeleteReservationMutation.Data deleteReservationResponse = client.executeAsync(new SandboxDeleteReservationMutation(deleteReservationInput)).get(); + DeleteSandboxReservationResponse deleteReservationResponse = client.deleteReservation(reservationData.getId()); + + System.out.println("Reservation Was Deleted: " + reservationData.getId()); + System.out.println(deleteReservationResponse); + // ******* Delete Property ******* - DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(propertyId).build(); - SandboxDeletePropertyMutation.Data deletePropertyResponse = client.executeAsync(new SandboxDeletePropertyMutation(deletePropertyInput)).get(); + DeleteSandboxPropertyResponse deletePropertyResponse = client.deleteProperty(propertyId); + + System.out.println("Property Was Deleted: " + propertyId); + System.out.println(deletePropertyResponse); + + System.exit(0); } - private static void deletePropertyIfExists() throws ExecutionException, InterruptedException { - SandboxPropertiesQuery propertiesQuery = SandboxPropertiesQuery.builder().skipReservations(true).build(); - SandboxPropertiesQuery.Data propertiesResponse = client.executeAsync(propertiesQuery).get(); + private static void deletePropertyIfExists() { + GetSandboxPropertiesResponse propertiesResponse = client.getProperties(); - propertiesResponse.properties.elements.forEach(property -> { - if (property.name.equals(PROPERTY_NAME) || property.name.equals(UPDATED_PROPERTY_NAME)) { - DeletePropertyInput deletePropertyInput = DeletePropertyInput.builder().id(property.id).build(); - client.executeAsync(new SandboxDeletePropertyMutation(deletePropertyInput)); + propertiesResponse.getData().forEach(property -> { + if (property.getName().equals(PROPERTY_NAME) || property.getName().equals(UPDATED_PROPERTY_NAME)) { + System.out.println("Deleting existing property: ID: " + property.getId() + ", Name: " + property.getName()); + client.deleteProperty(property.getId()); } }); } diff --git a/gradle.properties b/gradle.properties index 821b9fdc..dd8aa840 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ kotlin.code.style=official groupId=com.expediagroup -version=1.0.2-SNAPSHOT +version=1.0.6-SNAPSHOT artifactName=lodging-connectivity-sdk description=SDK for Lodging Connectivity APIs diff --git a/gradlew.bat b/gradlew.bat index 25da30db..7101f8e4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle index 174ff8bf..5f1e92b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,5 @@ rootProject.name = "lodging-connectivity-sdk" // Used as published artifactId include 'code' include 'examples' +include 'apollo-compiler-plugin' From 0720f0569e084328bbb1d9a2455b0d56f70c52fc Mon Sep 17 00:00:00 2001 From: Mohammad Dwairi <49045447+Mohammad-Dwairi@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:19:08 +0300 Subject: [PATCH 06/15] feat: add SDK base HTTP models (#104) Co-authored-by: mohnoor94 --- .../ExpediaGroupResponseParsingException.kt} | 22 +- .../service/ExpediaGroupAuthException.kt | 14 +- .../service/ExpediaGroupNetworkException.kt | 33 ++ .../bearer/BearerAuthenticationInterceptor.kt | 100 ++++++ .../bearer/BearerAuthenticationManager.kt | 118 +++++++ .../bearer/BearerTokenStorage.kt | 103 ++++++ .../authentication/bearer/TokenResponse.kt | 35 ++ .../common/AuthenticationManager.kt | 59 ++++ .../authentication/common/Credentials.kt | 54 +++ .../sdk/core2/client/RequestExecutor.kt | 78 +++++ .../sdk/core2/client/Transport.kt | 63 ++++ .../sdk/core2/extension/NullableExtension.kt | 13 + .../sdk/core2/http/ContentType.kt | 93 +++++ .../expediagroup/sdk/core2/http/Headers.kt | 214 ++++++++++++ .../expediagroup/sdk/core2/http/MediaType.kt | 148 ++++++++ .../expediagroup/sdk/core2/http/Protocol.kt | 50 +++ .../expediagroup/sdk/core2/http/Request.kt | 226 ++++++++++++ .../sdk/core2/http/RequestBody.kt | 140 ++++++++ .../expediagroup/sdk/core2/http/Response.kt | 249 +++++++++++++ .../sdk/core2/http/ResponseBody.kt | 105 ++++++ .../com/expediagroup/sdk/core2/http/Status.kt | 107 ++++++ .../com/expediagroup/sdk/core2/http/Url.kt | 326 ++++++++++++++++++ .../sdk/core2/interceptor/Interceptor.kt | 72 ++++ .../interceptor/InterceptorsChainExecutor.kt | 75 ++++ .../sdk/core2/logging/LoggingInterceptor.kt | 103 ++++++ .../sdk/core2/logging/common/Constant.kt | 19 + .../sdk/core2/logging/common/LogMessageTag.kt | 16 + .../logging/common/LoggableContentTypes.kt | 21 ++ .../core2/logging/common/LoggerDecorator.kt | 75 ++++ .../core2/logging/masking/JsonFieldFilter.kt | 19 + .../masking/JsonFieldPatternBuilder.kt | 17 + .../core2/logging/masking/MaskLogsUtils.kt | 100 ++++++ .../sdk/core2/okhttp/BaseOkHttpClient.kt | 60 ++++ .../core2/okhttp/OkHttpClientConfiguration.kt | 92 +++++ .../sdk/core2/okhttp/OkHttpTransport.kt | 112 ++++++ .../sdk/graphql/common/ApolloAliases.kt | 13 + .../sdk/graphql/common/ApolloHttpEngine.kt | 125 +++++++ .../graphql/common/DefaultGraphQLExecutor.kt | 79 ++--- .../sdk/graphql/common/GraphQLClient.kt | 9 + .../sdk/graphql/common/GraphQLExecutor.kt | 27 +- .../sdk/graphql/model/response/Error.kt | 15 +- .../common/DefaultRequestExecutor.kt | 49 +++ .../configuration/ClientConfiguration.kt | 269 +++++++-------- .../client/FileManagementClient.kt | 139 -------- .../models/FileUploadRequest.kt | 9 - .../filemanagement/models/GenericError.kt | 92 ----- .../models/Upload201Response.kt | 71 ---- .../exception/PropertyConstraintViolation.kt | 31 -- .../PropertyConstraintViolationException.kt | 31 -- .../operations/FileDownloadOperation.kt | 32 -- .../operations/FileUploadOperation.kt | 97 ------ .../PropertyConstraintsValidator.kt | 52 --- .../payment/PaymentClient.kt | 10 +- .../sandbox/SandboxDataManagementClient.kt | 10 +- .../supply/reservation/ReservationClient.kt | 11 +- .../FileManagementExample.java | 26 -- ...dboxDataManagementClientUsageExample.java} | 0 57 files changed, 3529 insertions(+), 799 deletions(-) rename code/src/main/kotlin/com/expediagroup/sdk/{graphql/extension/ApolloErrorExtension.kt => core/model/exception/client/ExpediaGroupResponseParsingException.kt} (52%) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupNetworkException.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/MediaType.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Protocol.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Request.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/RequestBody.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Response.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/ResponseBody.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Status.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/http/Url.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/Interceptor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/InterceptorsChainExecutor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt delete mode 100644 examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java rename examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/{LodgingSupplySandboxDataManagementClientUsageExample.java => SandboxDataManagementClientUsageExample.java} (100%) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupResponseParsingException.kt similarity index 52% rename from code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupResponseParsingException.kt index 2963f44d..e8c1cbfc 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupResponseParsingException.kt @@ -14,12 +14,18 @@ * limitations under the License. */ -package com.expediagroup.sdk.graphql.extension +package com.expediagroup.sdk.core.model.exception.client -import com.apollographql.apollo.api.Error - -fun Error.toSDKError() = - com.expediagroup.sdk.graphql.model.response.Error( - message = this.message, - path = this.path?.map { it.toString() } - ) +/** + * Exception thrown when the SDK fails to parse a service response. + * + * This is a client-side exception that indicates the response was received + * but could not be properly deserialized into the expected format. + * + * @param message A description of the parsing failure + * @param cause The underlying parsing/mapping exception + */ +class ExpediaGroupResponseParsingException( + message: String? = null, + cause: Throwable? = null, +) : ExpediaGroupClientException(message, cause) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt index c3cd52ea..e3fcd463 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt @@ -38,6 +38,16 @@ class ExpediaGroupAuthException( constructor( status: HttpStatus, message: String, - transactionId: String? - ) : this(message = "[${status.code}] $message", transactionId = transactionId) + ) : this(message = "[${status.code}] $message") + + /** + * An exception that is thrown when an authentication error occurs. + * + * @param status The HTTP status of the error (as an integer). + * @param message The error message. + */ + constructor( + status: Int, + message: String, + ) : this(HttpStatus.fromCode(status), message) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupNetworkException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupNetworkException.kt new file mode 100644 index 00000000..8548ba28 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupNetworkException.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core.model.exception.service + +/** + * Exception thrown when network-related errors occur during service operations. + * + * This exception wraps network-level failures that occur while communicating with + * Expedia Group services (e.g., connection timeouts, DNS failures, SSL/TLS errors). + * + * @param message A human-readable description of the network error + * @param cause The underlying exception that caused this network error + * @param transactionId Unique identifier for tracking this request across systems + */ +class ExpediaGroupNetworkException( + message: String? = null, + cause: Throwable? = null, + transactionId: String? = null +) : ExpediaGroupServiceException(message, cause, transactionId) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt new file mode 100644 index 00000000..81413365 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.bearer + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException +import com.expediagroup.sdk.core2.authentication.common.Credentials +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import com.expediagroup.sdk.core2.interceptor.Interceptor +import java.io.IOException +import kotlin.jvm.Throws + +/** + * An interceptor that handles bearer token-based authentication for HTTP requests. + * + * This interceptor ensures that an up-to-date bearer token is added to the `Authorization` header of each HTTP request. + * It manages token expiration and re-authentication automatically. If the token is about to expire, the interceptor + * synchronously refreshes it before proceeding with the request. + * + * Requests to the `authUrl` (authentication endpoint) are excluded from this behavior to prevent recursive authentication loops. + * + * @param transport The [Transport] used for making authentication requests. + * @param authUrl The URL of the authentication endpoint used to retrieve bearer tokens. + * @param credentials The [Credentials] required for authentication. + */ +class BearerAuthenticationInterceptor( + transport: Transport, + private val authUrl: String, + credentials: Credentials +) : Interceptor { + private val bearerAuthenticationManager = BearerAuthenticationManager(transport, authUrl, credentials) + private val lock = Any() + + /** + * Intercepts the HTTP request, adding a bearer token to the `Authorization` header. + * + * This method checks if the token needs to be refreshed and does so if necessary. It excludes + * requests targeting the `authUrl` from this behavior to avoid recursive authentication requests. + * + * @param chain The [Interceptor.Chain] responsible for managing the request and its progression. + * @return The [Response] resulting from the executed request. + * @throws ExpediaGroupAuthException If authentication fails due to invalid credentials or server errors + */ + @Throws(ExpediaGroupAuthException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + + if (isAuthenticationRequest(request)) { + return chain.proceed(request) + } + + ensureValidAuthentication() + + val authorizedRequest = request.newBuilder() + .addHeader("Authorization", bearerAuthenticationManager.getAuthorizationHeaderValue()) + .build() + + return chain.proceed(authorizedRequest) + } + + /** + * Checks if the given request is for authentication. + */ + private fun isAuthenticationRequest(request: Request): Boolean = request.url.toString() == authUrl + + /** + * Ensures there is a valid authentication token available. + * If needed, authenticates under a synchronization lock to prevent multiple simultaneous authentications. + * + * @throws ExpediaGroupAuthException If authentication fails + */ + private fun ensureValidAuthentication() { + try { + if (bearerAuthenticationManager.needsAuthentication()) { + synchronized(lock) { + if (bearerAuthenticationManager.needsAuthentication()) { + bearerAuthenticationManager.authenticate() + } + } + } + } catch (e: IOException) { + throw ExpediaGroupAuthException("Failed to authenticate", e) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt new file mode 100644 index 00000000..f2ae99d4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.bearer + +import com.expediagroup.sdk.core.extension.getOrThrow +import com.expediagroup.sdk.core.http.HttpStatus +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException +import com.expediagroup.sdk.core2.authentication.common.AuthenticationManager +import com.expediagroup.sdk.core2.authentication.common.Credentials +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.http.ContentType +import com.expediagroup.sdk.core2.http.RequestBody +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +/** + * Manages bearer token authentication for HTTP requests. + * + * The `BearerAuthenticationManager` handles the lifecycle of bearer tokens, including retrieval, storage, + * and validation. It interacts with an authentication server to fetch tokens using client credentials, + * ensures tokens are refreshed when necessary, and provides them in the required format for authorization headers. + * + * @param transport The [Transport] used to execute authentication requests. + * @param authUrl The URL of the authentication server's endpoint to obtain bearer tokens. + * @param credentials The [Credentials] containing the client key and secret used for authentication. + */ +class BearerAuthenticationManager( + private val transport: Transport, + private val authUrl: String, + private val credentials: Credentials +) : AuthenticationManager { + + @Volatile + private var bearerTokenStorage = BearerTokenStorage.empty + + private val objectMapper = ObjectMapper() + .registerKotlinModule() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + override fun authenticate() { + clearAuthentication() + + val request = createAuthenticationRequest() + val response = executeAuthenticationRequest(request) + val tokenResponse = parseAuthenticationResponse(response) + storeToken(tokenResponse) + } + + override fun needsAuthentication(): Boolean = bearerTokenStorage.isAboutToExpire() + + override fun clearAuthentication() { + bearerTokenStorage = BearerTokenStorage.empty + } + + /** + * Retrieves the stored token formatted as an `Authorization` header value. + * + * @return The token in the format `Bearer ` for use in HTTP headers. + */ + fun getAuthorizationHeaderValue(): String = bearerTokenStorage.getAsAuthorizationHeaderValue() + + private fun createAuthenticationRequest(): Request = + Request.Builder() + .url(authUrl) + .method("POST", RequestBody.create(mapOf("grant_type" to "client_credentials"))) + .header("Authorization", credentials.encodeBasic()) + .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.mimeType) + .build() + + private fun executeAuthenticationRequest(request: Request): Response { + val response = transport.execute(request) + if (!response.isSuccessful) { + throw ExpediaGroupAuthException(response.code, "Authentication failed") + } + return response + } + + private fun parseAuthenticationResponse(response: Response): TokenResponse { + val responseBody = response.body.getOrThrow { + ExpediaGroupAuthException(HttpStatus.INTERNAL_SERVER_ERROR, "Authentication response body is empty") + } + + val responseString = responseBody.source().use { + it.readString(responseBody.contentType()?.charset ?: Charsets.UTF_8) + } + + return try { + objectMapper.readValue(responseString, TokenResponse::class.java) + } catch (e: Exception) { + throw ExpediaGroupResponseParsingException("Failed to parse authentication response", e) + } + } + + private fun storeToken(tokenResponse: TokenResponse) { + bearerTokenStorage = BearerTokenStorage.create( + accessToken = tokenResponse.accessToken, + expiresIn = tokenResponse.expiresIn + ) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt new file mode 100644 index 00000000..416d5f17 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.bearer + +import java.time.Clock +import java.time.Instant + +/** + * Stores and manages a bearer token and its expiration details. + * + * The `BearerTokenStorage` class is responsible for encapsulating the bearer token along with its + * expiration time. It provides utilities to check if the token is about to expire and to format + * the token as an `Authorization` header value. + * + * @param accessToken The bearer token. + * @param expiresIn The time in seconds until the token expires, relative to when it was issued. + * @param expirationBufferSeconds The number of seconds before the token's expiration time that it is considered "about to expire". + * @param clock The clock to use for time-based operations. Defaults to system clock. + */ +class BearerTokenStorage private constructor( + val accessToken: String, + val expiresIn: Long, + private val expirationBufferSeconds: Long, + private val clock: Clock, + private val expiryInstant: Instant +) { + + /** + * Checks if the bearer token is about to expire. + * + * A token is considered "about to expire" if the current time is within the configured buffer + * of the token's expiration time. + * + * @return `true` if the token is about to expire; `false` otherwise. + */ + fun isAboutToExpire(): Boolean = + Instant.now(clock).isAfter(expiryInstant.minusSeconds(expirationBufferSeconds)) + + /** + * Formats the bearer token as an `Authorization` header value. + * + * @return The token in the format `Bearer `. + */ + fun getAsAuthorizationHeaderValue(): String = "Bearer $accessToken" + + companion object { + private const val DEFAULT_EXPIRATION_BUFFER_SECONDS = 60L + + /** + * Creates an empty bearer token storage instance. + * This instance will always report as expired. + */ + val empty: BearerTokenStorage = create("", -1) + + /** + * Creates a new bearer token storage instance with default settings. + * + * @param accessToken The bearer token + * @param expiresIn The time in seconds until the token expires + * @param expirationBufferSeconds Optional buffer time before expiration. Defaults to 60 seconds. + * @param clock Optional clock for time operations. Defaults to system clock. + * @return A new BearerTokenStorage instance + */ + fun create( + accessToken: String, + expiresIn: Long, + expirationBufferSeconds: Long = DEFAULT_EXPIRATION_BUFFER_SECONDS, + clock: Clock = Clock.systemUTC() + ): BearerTokenStorage { + val expiryInstant = if (expiresIn >= 0) { + Instant.now(clock).plusSeconds(expiresIn) + } else { + Instant.EPOCH + } + + return BearerTokenStorage( + accessToken = accessToken, + expiresIn = expiresIn, + expirationBufferSeconds = expirationBufferSeconds, + clock = clock, + expiryInstant = expiryInstant + ) + } + } + + override fun toString(): String { + return "BearerTokenStorage(expiresIn=$expiresIn, expirationBufferSeconds=$expirationBufferSeconds, expiryInstant=$expiryInstant)" + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt new file mode 100644 index 00000000..a3dd271f --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.bearer + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Represents the response from an authentication server containing a bearer token and its expiration details. + * + * The `TokenResponse` class is used to deserialize the response from an authentication server. It includes + * the bearer token and the duration (in seconds) until the token expires. + * + * @param accessToken The bearer token issued by the authentication server. + * @param expiresIn The time in seconds until the token expires, starting from when it was issued. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +data class TokenResponse( + @JsonProperty("access_token") val accessToken: String, + @JsonProperty("expires_in") val expiresIn: Long +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt new file mode 100644 index 00000000..33746755 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.common + +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException + +/** + * Defines the contract for managing authentication within the SDK. + * + * An `AuthenticationManager` is responsible for handling the process of authenticating with an external + * service or API and maintaining the authentication state. Implementations should handle token lifecycle, + * including acquisition, storage, and renewal. + */ +interface AuthenticationManager { + /** + * Performs the authentication process, obtaining the necessary credentials or tokens. + * + * This method is responsible for executing the authentication logic, such as sending requests to an + * authentication server, handling the response, and storing the retrieved credentials or tokens for future use. + * + * @throws ExpediaGroupAuthException If authentication fails due to invalid credentials or server errors + * @throws ExpediaGroupResponseParsingException If the authentication response cannot be parsed + * @throws ExpediaGroupNetworkException If a network error occurs during authentication + */ + @Throws( + ExpediaGroupAuthException::class, + ExpediaGroupResponseParsingException::class, + ExpediaGroupNetworkException::class + ) + fun authenticate() + + /** + * Checks if the current authentication state is valid and not about to expire. + * + * @return true if new authentication is needed, false otherwise + */ + fun needsAuthentication(): Boolean + + /** + * Clears any stored authentication state. + */ + fun clearAuthentication() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt new file mode 100644 index 00000000..2d865c49 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.authentication.common + +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets.ISO_8859_1 +import java.util.Base64 + +/** + * Represents a set of credentials consisting of a key and a secret. + * + * The `Credentials` class encapsulates authentication details required for accessing secure resources. + * It provides functionality to encode the credentials into a `Basic` authentication header value. + * + * @param key The client key or username for authentication. + * @param secret The client secret or password for authentication. + */ +data class Credentials( + private val key: String, + private val secret: String +) { + /** + * Encodes the credentials into a `Basic` authentication header value. + * + * This method combines the `key` and `secret` into a single string in the format `key:secret`, + * encodes it using Base64, and prefixes it with `Basic`. The resulting string is suitable for use + * in the `Authorization` header of HTTP requests. + * + * @param charset The character set to use for encoding the credentials. Defaults to [ISO_8859_1]. + * @return The `Basic` authentication header value as a string. + */ + fun encodeBasic(charset: Charset = ISO_8859_1): String { + val keyAndSecret = "$key:$secret" + val bytes = keyAndSecret.toByteArray(charset) + val encoded = Base64.getEncoder().encodeToString(bytes) + return "Basic $encoded" + } + + override fun toString(): String = "Credentials(key=$key)" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt new file mode 100644 index 00000000..1b2e617e --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.client + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import com.expediagroup.sdk.core2.interceptor.Interceptor + +/** + * Abstract base class for processing HTTP requests within the SDK. + * + * This class serves as the main entry point for executing HTTP requests through the SDK. + * It enhances the basic [Transport] functionality by: + * + * 1. Applying request/response interceptors + * 2. Enforcing SDK-specific policies and rules (e.g. authentication) + * 3. Providing common error handling and retry logic (if needed) + * 4. Managing request/response lifecycle and transformation + * + * Implementations should: + * - Define the order and types of interceptors to be applied + * - Implement any SDK-specific error handling or retry logic + * - Handle request/response transformation and validation + * + * Example implementation: + * ``` + * class SdkRequestProcessor(transport: Transport) : RequestProcessor(transport) { + * override val interceptors = listOf( + * AuthenticationInterceptor(), + * LoggingInterceptor(), + * RetryInterceptor() + * ) + * + * override fun execute(request: Request) = executeWithInterceptors(request) + * } + * ``` + * + * @param transport The transport implementation to use for executing requests + */ +abstract class RequestExecutor(private val transport: Transport) { + /** + * List of interceptors to be applied to requests in order. + * + * Interceptors can modify requests before they are sent and responses + * before they are returned to the caller. Common use cases include: + * - Adding authentication headers + * - Logging + * - Retry logic + * - Request/response validation + * - Error handling + */ + protected abstract val interceptors: List + + /** + * Executes an HTTP request synchronously, applying all configured interceptors. + * + * @param request The request to execute + * @return The response from the server after passing through interceptors + * @throws ExpediaGroupNetworkException If any network-related error occurs + */ + @Throws(ExpediaGroupNetworkException::class) + abstract fun execute(request: Request): Response +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt new file mode 100644 index 00000000..7119f02b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.client + +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response + +/** + * A transport layer interface that adapts different HTTP client libraries to work with the SDK. + * + * This interface serves as an abstraction layer between the SDK and the underlying HTTP client, + * allowing users to integrate their preferred HTTP client library while maintaining consistent + * behavior across the SDK. Implementers are responsible for: + * + * 1. Converting SDK request/response models to their HTTP client's models + * 2. Handling HTTP client-specific configuration and setup + * 3. Managing resources and connections appropriately + * + * Example implementation using OkHttp: + * ``` + * class OkHttpTransport(private val client: OkHttpClient) : Transport { + * override fun execute(request: Request): Response { + * val okHttpRequest = request.toOkHttpRequest() + * return client.newCall(okHttpRequest).execute().toSdkResponse() + * } + * } + * ``` + * + * @see Request SDK request model that wraps HTTP request details + * @see Response SDK response model that wraps HTTP response details + */ +interface Transport { + /** + * Executes an HTTP request synchronously. + * + * This method should: + * - Convert the SDK request to the HTTP client's request format + * - Execute the request using the underlying HTTP client + * - Convert the HTTP client's response to the SDK response format + * - Handle errors appropriately by throwing [ExpediaGroupNetworkException] + * + * @param request The SDK request to execute + * @return The response from the server wrapped in the SDK response model + * @throws ExpediaGroupNetworkException If any network-related error occurs during execution + */ + @Throws(ExpediaGroupNetworkException::class) + fun execute(request: Request): Response +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt new file mode 100644 index 00000000..e3af6928 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt @@ -0,0 +1,13 @@ +package com.expediagroup.sdk.core2.extension + +import java.nio.charset.Charset + +inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { + return this ?: throw exceptionProvider() +} + +fun Boolean?.orFalseIfNull(): Boolean = this == true + +fun String?.orNullIfBlank(): String? = this?.takeUnless { it.isBlank() } + +fun Charset?.orUtf8() = this ?: Charsets.UTF_8 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt new file mode 100644 index 00000000..12e0f59d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import com.expediagroup.sdk.core2.http.ContentType.entries + + +/** + * Enum representing various content types, with their associated MIME type strings. + * + * This enum provides a predefined set of commonly used MIME types for applications, + * making it easy to manage and reference content types in a consistent manner. + * + * @param mimeType The MIME type string associated with the content type. + */ +enum class ContentType(val mimeType: String) { + /** + * MIME type for Atom feeds. + */ + APPLICATION_ATOM_XML("application/atom+xml"), + + /** + * MIME type for JSON data. + */ + APPLICATION_JSON("application/json"), + + /** + * MIME type for generic XML data. + */ + APPLICATION_XML("application/xml"), + + /** + * MIME type for form data submitted as URL-encoded values. + */ + APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded"), + + /** + * MIME type for SOAP XML messages. + */ + APPLICATION_SOAP_XML("application/soap+xml"), + + /** + * MIME type for XHTML documents. + */ + APPLICATION_XHTML_XML("application/xhtml+xml"), + + /** + * MIME type for plain text. + */ + TEXT_PLAIN("text/plain"), + + /** + * MIME type for HTML documents. + */ + TEXT_HTML("text/html"), + + /** + * MIME type for XML text. + */ + TEXT_XML("text/xml"), + + /** + * Wildcard MIME type for any text-based content. + */ + DEFAULT_TEXT("text/*"); + + companion object { + /** + * Finds a [ContentType] by its MIME type string. + * + * @param mimeType The MIME type string to search for. + * @return The matching [ContentType], or `null` if no match is found. + */ + fun fromMimeType(mimeType: String): ContentType? { + return entries.find { it.mimeType.equals(mimeType, ignoreCase = true) } + } + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt new file mode 100644 index 00000000..6ced1c2b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.util.Locale + +/** + * Represents a collection of HTTP headers. + * This class is immutable and thread-safe. + */ +class Headers private constructor(private val headersMap: Map>) { + + /** + * Returns the first header value for the given name, or null if none. + * + * @param name the header name (case-insensitive) + * @return the first header value, or null if not found + * @throws IllegalArgumentException if [name] is null + */ + fun get(name: String): String? = headersMap[name.lowercase(Locale.US)]?.firstOrNull() + + /** + * Returns all header values for the given name. + * + * @param name the header name (case-insensitive) + * @return an unmodifiable list of header values, or an empty list if none + * @throws IllegalArgumentException if [name] is null + */ + fun values(name: String): List = headersMap[name.lowercase(Locale.US)] ?: emptyList() + + /** + * Returns an unmodifiable set of all header names. + * + * @return an unmodifiable set of header names + */ + fun names(): Set = headersMap.keys + + /** + * Returns an unmodifiable list of all header entries. + * + * @return an unmodifiable list of header entries as [Map.Entry] + */ + fun entries(): Set>> = headersMap.entries + + /** + * Returns a new [Builder] initialized with the existing headers. + * + * @return a new [Builder] + */ + fun newBuilder(): Builder = Builder(this) + + override fun toString(): String = buildString { + headersMap.forEach { + append(it.key) + append(": ") + append(it.value.toString()) + append("\n") + } + } + + /** + * Builder for constructing [Headers] instances. + */ + class Builder { + + private val headersMap: MutableMap> = LinkedHashMap() + + /** + * Creates a new builder + */ + constructor() + + /** + * Creates a new builder initialized with the headers from [headers]. + * + * @param headers the headers to initialize from + */ + constructor(headers: Headers) : this() { + headers.headersMap.forEach { (key, values) -> + headersMap[key] = values.toMutableList() + } + } + + /** + * Adds a header with the specified name and value. + * Multiple headers with the same name are allowed. + * + * @param name the header name + * @param value the header value + * @return this builder + * @throws IllegalArgumentException if [name] or [value] is invalid + */ + @Throws(IllegalArgumentException::class) + fun add(name: String, value: String): Builder { + return add(name, listOf(value)) + } + + /** + * Adds all header values for the specified name. + * + * @param name the header name + * @param values the list of header values + * @return this builder + * @throws IllegalArgumentException if [name] or any [values] are invalid + */ + @Throws(IllegalArgumentException::class) + fun add(name: String, values: List): Builder { + headersMap.computeIfAbsent(processHeaderName(name)) { mutableListOf() }.addAll(processHeaderValues(values)) + return this + } + + /** + * Sets the header with the specified name to the single value provided. + * If headers with this name already exist, they are removed. + * + * @param name the header name + * @param value the header value + * @return this builder + * @throws IllegalArgumentException if [name] or [value] is invalid + */ + @Throws(IllegalArgumentException::class) + fun set(name: String, value: String): Builder { + return set(name, listOf(value)) + } + + /** + * Sets the header with the specified name to the values list provided. + * If headers with this name already exist, they are removed. + * + * @param name the header name + * @param values the header value + * @return this builder + * @throws IllegalArgumentException if [name] or [values] are invalid + */ + @Throws(IllegalArgumentException::class) + fun set(name: String, values: List): Builder { + val processedName = processHeaderName(name) + + remove(processedName) + add(processedName, processHeaderValues(values)) + + return this + } + + /** + * Removes any header with the specified name. + * + * @param name the header name + * @return this builder + */ + fun remove(name: String): Builder { + headersMap.remove(name.lowercase(Locale.US)) + return this + } + + /** + * Builds an immutable [Headers] instance. + * + * @return the built [Headers] + */ + fun build(): Headers { + return Headers(headersMap) + } + + private fun processHeaderName(name: String): String = name + .lowercase(Locale.US) + .trim() + .let { + validateHeaderName(it) + return@let it + } + + private fun processHeaderValues(values: List): List = values + .map { + it.trim() + } + .let { + it.forEach { value -> validateHeaderValue(value) } + return@let it + } + + private fun validateHeaderName(name: String) { + require(name.isNotBlank()) { "Header name must not be blank" } + + for (char in name) { + require(char in '!'..'\u007E') { + String.format("Invalid character %#04x in header name: %s", char.code, name) + } + } + } + + private fun validateHeaderValue(value: String) { + for (char in value) { + require(char == '\t' || (char in '\u0020'..'\u007E')) { + String.format("Invalid character %#04x in header value: %s", char.code, value) + } + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/MediaType.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/MediaType.kt new file mode 100644 index 00000000..bed4d26f --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/MediaType.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.nio.charset.Charset +import java.nio.charset.UnsupportedCharsetException +import java.util.Locale +import java.util.regex.Pattern + +/** + * Represents an immutable media type as defined in RFC 7231. + * + * @property type The primary type (e.g., "text", "application"). + * @property subtype The subtype (e.g., "plain", "json"). + * @property parameters The map of parameters associated with the media type (e.g., charset). + */ +class MediaType private constructor( + val type: String, + val subtype: String, + val parameters: Map +) { + + /** + * Returns the charset specified in the media type, or null if none. + */ + val charset: Charset? + get() { + val charsetName = parameters["charset"] ?: return null + return try { + Charset.forName(charsetName) + } catch (e: UnsupportedCharsetException) { + null + } + } + + /** + * Returns a new [MediaType] instance with the given [charset] parameter. + * + * @param charset The charset to set. + * @return A new [MediaType] with the specified charset. + */ + fun withCharset(charset: Charset): MediaType { + val newParameters = parameters.toMutableMap() + newParameters["charset"] = charset.name() + return MediaType(type, subtype, newParameters.toMap()) + } + + override fun toString(): String { + val params = parameters.entries.joinToString("; ") { (name, value) -> + "$name=${quoteIfNeeded(value)}" + } + return "$type/$subtype${if (params.isNotEmpty()) "; $params" else ""}" + } + + companion object { + private const val TOKEN_REGEX = "[!#$%&'*+\\-.^_`|~A-Za-z0-9]+" + private const val QUOTED_STRING_REGEX = "\"(\\\\[\\s\\S]|[^\\\\\"])*\"" + + private val TYPE_SUBTYPE_PATTERN = Pattern.compile( + "($TOKEN_REGEX)/($TOKEN_REGEX)", Pattern.CASE_INSENSITIVE + ) + + private val PARAMETER_PATTERN = Pattern.compile( + ";\\s*($TOKEN_REGEX)\\s*=\\s*($TOKEN_REGEX|$QUOTED_STRING_REGEX)", Pattern.CASE_INSENSITIVE + ) + + /** + * Parses a media type string into a [MediaType] object. + * + * @param mediaType The media type string to parse. + * @return The parsed [MediaType], or null if parsing fails. + */ + fun parse(mediaType: String): MediaType? { + require(mediaType.isNotBlank()) { "mediaType must not be blank" } + + val typeSubtypeMatcher = TYPE_SUBTYPE_PATTERN.matcher(mediaType) + if (!typeSubtypeMatcher.lookingAt()) { + return null + } + + val type = typeSubtypeMatcher.group(1).lowercase(Locale.US) + val subtype = typeSubtypeMatcher.group(2).lowercase(Locale.US) + val parameters = mutableMapOf() + + val parameterMatcher = PARAMETER_PATTERN.matcher(mediaType) + var s = typeSubtypeMatcher.end() + while (s < mediaType.length) { + parameterMatcher.region(s, mediaType.length) + if (!parameterMatcher.lookingAt()) { + return null + } + + val name = parameterMatcher.group(1).lowercase(Locale.US) + val valueToken = parameterMatcher.group(2) + val value = if (valueToken.startsWith("\"")) { + unquote(valueToken) + } else { + valueToken + } + parameters[name] = value + s = parameterMatcher.end() + } + + return MediaType(type, subtype, parameters.toMap()) + } + + private fun unquote(quoted: String): String { + val sb = StringBuilder() + var i = 1 // Skip initial quote + while (i < quoted.length - 1) { // Skip ending quote + val c = quoted[i] + if (c == '\\') { + i++ + if (i < quoted.length - 1) { + sb.append(quoted[i]) + } + } else { + sb.append(c) + } + i++ + } + return sb.toString() + } + + private fun quoteIfNeeded(value: String): String { + return if (TOKEN_REGEX.toRegex().matches(value)) { + value + } else { + "\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\"" + } + } + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Protocol.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Protocol.kt new file mode 100644 index 00000000..c4a7dbac --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Protocol.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http; + +/** + * Enumeration of HTTP protocols. + */ +enum class Protocol { + HTTP_1_0, + HTTP_1_1, + HTTP_2, + H2_PRIOR_KNOWLEDGE, + QUIC; + + companion object { + /** + * Parses a protocol string to a [Protocol] enum. + */ + fun get(protocol: String): Protocol = when (protocol.uppercase()) { + "HTTP/1.0" -> HTTP_1_0 + "HTTP/1.1" -> HTTP_1_1 + "HTTP/2", "HTTP/2.0" -> HTTP_2 + "H2_PRIOR_KNOWLEDGE" -> H2_PRIOR_KNOWLEDGE + "QUIC" -> QUIC + else -> throw IllegalArgumentException("Unexpected protocol: $protocol") + } + } + + override fun toString(): String = when (this) { + HTTP_1_0 -> "http/1.0" + HTTP_1_1 -> "http/1.1" + HTTP_2 -> "http/2" + H2_PRIOR_KNOWLEDGE -> "h2_prior_knowledge" + QUIC -> "quic" + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Request.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Request.kt new file mode 100644 index 00000000..af9b3ae3 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Request.kt @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.util.Locale + +/** + * Represents an immutable HTTP request. + * + * Use [Builder] to create an instance. + */ +class Request private constructor( + val method: String, + val url: Url, + val headers: Headers, + val body: RequestBody?, + val tags: Map, Any> +) { + + /** + * Returns the tag associated with the given [type], or null if none. + * + * @param type The class type of the tag. + * @return The tag object, or null if none. + */ + @Suppress("UNCHECKED_CAST") + fun tag(type: Class): T? = tags[type] as? T + + /** + * Returns the tag associated with [Any] as key, or null if none. + * + * @return The tag object, or null if none. + */ + fun tag(): Any? = tag(Any::class.java) + + /** + * Returns a new [Builder] initialized with this request's data. + * + * @return A new builder. + */ + fun newBuilder(): Builder = Builder(this) + + /** + * Builder class for [Request]. + */ + class Builder { + private var method: String? = null + private var url: Url? = null + private var headers: Headers.Builder = Headers.Builder() + private var body: RequestBody? = null + private var tags: MutableMap, Any> = mutableMapOf() + + /** + * Creates a new builder. + */ + constructor() + + /** + * Creates a builder initialized with the data from [request]. + * + * @param request The request to copy data from. + */ + constructor(request: Request) { + this.method = request.method + this.url = request.url + this.headers = request.headers.newBuilder() + this.body = request.body + this.tags = request.tags.toMutableMap() + } + + /** + * Sets the HTTP method. + * + * @param method HTTP method, e.g., GET, POST. + * @param body Optional request body. + * @return This builder. + * @throws IllegalArgumentException If [method] is empty. + */ + fun method(method: String, body: RequestBody? = null) = apply { + require(method.isNotEmpty()) { "Method cannot be empty" } + val upperMethod = method.uppercase(Locale.US) + this.method = upperMethod + this.body = body + } + + /** + * Sets the URL. + * + * @param url The URL as a string. + * @return This builder. + * @throws IllegalArgumentException If [url] is invalid. + */ + fun url(url: String) = apply { + val parsedUrl = Url.parse(url) ?: throw IllegalArgumentException("Invalid URL: $url") + this.url = parsedUrl + } + + /** + * Sets the URL. + * + * @param url The URL as an [Url] object. + * @return This builder. + */ + fun url(url: Url) = apply { + this.url = url + } + + /** + * Adds a header with the specified name and value. + * + * @param name The header name. + * @param value The header value. + * @return This builder. + * @throws IllegalArgumentException If [name] or [value] is invalid. + */ + fun addHeader(name: String, value: String) = apply { + headers.add(name, value) + } + + /** + * Adds a header with the specified name and values. + * + * @param name The header name. + * @param values The header values list. + * @return This builder. + * @throws IllegalArgumentException If [name] or [values] are invalid. + */ + fun addHeader(name: String, values: List) = apply { + headers.add(name, values) + } + + /** + * Sets a header with the specified name and value, replacing any existing values. + * + * @param name The header name. + * @param value The header value. + * @return This builder. + * @throws IllegalArgumentException If [name] or [value] is invalid. + */ + fun header(name: String, value: String) = apply { + headers.set(name, value) + } + + /** + * Sets a header with the specified name and values list, replacing any existing values. + * + * @param name The header name. + * @param values The header values list. + * @return This builder. + * @throws IllegalArgumentException If [name] or [values] are invalid. + */ + fun header(name: String, values: List) = apply { + headers.set(name, values) + } + + /** + * Removes all headers with the specified name. + * + * @param name The header name. + * @return This builder. + * @throws IllegalArgumentException If [name] is null. + */ + fun removeHeader(name: String) = apply { + headers.remove(name) + } + + /** + * Sets the request body. + * + * @param body The request body. + * @return This builder. + */ + fun body(body: RequestBody) = apply { + this.body = body + } + + /** + * Adds a tag to the request. + * + * @param type The class type of the tag. + * @param tag The tag object, or null to remove it. + * @return This builder. + * @throws IllegalArgumentException If [type] is null. + */ + fun tag(type: Class, tag: T?) = apply { + if (tag == null) { + tags.remove(type) + } else { + tags[type] = tag + } + } + + /** + * Builds the [Request]. + * + * @return The built request. + * @throws IllegalStateException If the request is invalid. + */ + fun build(): Request { + val method = this.method ?: throw IllegalStateException("Method is required.") + val url = this.url ?: throw IllegalStateException("URL is required.") + + return Request( + method = method, + url = url, + headers = headers.build(), + body = body, + tags = tags + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/RequestBody.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/RequestBody.kt new file mode 100644 index 00000000..134730b9 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/RequestBody.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.io.IOException +import java.io.InputStream +import java.net.URLEncoder +import java.nio.charset.Charset +import okio.BufferedSink +import okio.Source +import okio.source + +/** + * Represents an HTTP request body. + * + * This class is immutable and thread-safe. + */ +abstract class RequestBody { + + /** + * Returns the media type of the request body. + */ + abstract fun contentType(): MediaType? + + /** + * Returns the number of bytes that will be written to [writeTo], or -1 if unknown. + */ + open fun contentLength(): Long = -1 + + /** + * Writes the request body to the given [sink]. + * + * @param sink the sink to write to. + * @throws IOException if an I/O error occurs. + */ + @Throws(IOException::class) + abstract fun writeTo(sink: BufferedSink) + + companion object { + /** + * Creates a new request body that reads from the given [inputStream]. + * + * @param mediaType the media type, or null if unknown. + * @param contentLength the length of the content, or -1 if unknown. + * @param inputStream the input stream to read from. + * @return a new [RequestBody] instance. + */ + fun create( + inputStream: InputStream, + mediaType: MediaType?, + contentLength: Long = -1 + ): RequestBody { + return object : RequestBody() { + override fun contentType(): MediaType? = mediaType + + override fun contentLength(): Long = contentLength + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { + inputStream.use { + sink.writeAll(it.source()) + } + } + } + } + + /** + * Creates a new request body that reads from the given [source]. + * + * @param mediaType the media type, or null if unknown. + * @param contentLength the length of the content, or -1 if unknown. + * @param source the source to read from. + * @return a new [RequestBody] instance. + */ + fun create( + source: Source, + mediaType: MediaType?, + contentLength: Long = -1 + ): RequestBody { + return object : RequestBody() { + override fun contentType(): MediaType? = mediaType + + override fun contentLength(): Long = contentLength + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { + source.use { src -> + sink.writeAll(src) + } + } + } + } + + /** + * Creates a new request body for form data with content type "application/x-www-form-urlencoded". + * + * @param formData The form data as a map of parameter names and values. + * @param charset The character set to use; defaults to UTF-8. + * @return A new [RequestBody] instance. + * @throws IllegalArgumentException If [formData] is null. + */ + fun create( + formData: Map, + charset: Charset = Charsets.UTF_8 + ): RequestBody { + + val mediaType = MediaType.parse(ContentType.APPLICATION_FORM_URLENCODED.mimeType)?.withCharset(charset) + ?: throw IllegalStateException("Failed to parse media type") + + val encodedForm = formData.map { (key, value) -> + "${encode(key, charset)}=${encode(value, charset)}" + }.joinToString("&") + + val contentBytes = encodedForm.toByteArray(charset) + + return create(contentBytes.inputStream(), mediaType) + } + + private fun encode(value: String, charset: Charset): String { + return URLEncoder.encode(value, charset.name()) + .replace("+", "%20") + .replace("*", "%2A") + .replace("%7E", "~") + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Response.kt new file mode 100644 index 00000000..bf145124 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Response.kt @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.io.Closeable +import java.io.IOException + +/** + * Represents an immutable HTTP response. + * + * Use [Builder] to create an instance. + */ +class Response private constructor( + val request: Request, + val protocol: Protocol, + val code: Int, + val message: String, + val headers: Headers, + val body: ResponseBody? +) : Closeable { + + /** + * Returns the header value for [name], or null if not present. + * + * @param name The name of the header. + * @return The value of the header, or null if not present. + */ + fun header(name: String): String? = headers.get(name) + + /** + * Returns all header values for [name]. + * + * @param name The name of the header. + * @return A list of header values. + */ + fun headers(name: String): List = headers.values(name) + + /** + * Returns true if the response code is in the 200-299 range. + */ + val isSuccessful: Boolean + get() = code in 200..299 + + /** + * Returns a new [Builder] initialized with this response's data. + * + * @return A new builder. + */ + fun newBuilder(): Builder = Builder(this) + + /** + * Closes the response body and releases any resources. + * + * After calling this method, the response body cannot be read. + * + * @throws IOException If an I/O error occurs. + */ + @Throws(IOException::class) + override fun close() { + body?.close() + } + + /** + * Builder class for [Response]. + */ + class Builder { + private var request: Request? = null + private var protocol: Protocol? = null + private var code: Int = -1 + private var message: String? = null + private var headers: Headers.Builder = Headers.Builder() + private var body: ResponseBody? = null + + /** + * Creates an empty builder. + */ + constructor() + + /** + * Creates a builder initialized with the data from [response]. + * + * @param response The response to copy data from. + */ + constructor(response: Response) { + this.request = response.request + this.protocol = response.protocol + this.code = response.code + this.message = response.message + this.headers = response.headers.newBuilder() + this.body = response.body + } + + /** + * Sets the request that initiated this response. + * + * @param request The originating request. + * @return This builder. + */ + fun request(request: Request) = apply { + this.request = request + } + + /** + * Sets the protocol used for the response. + * + * @param protocol The protocol (e.g., HTTP/1.1). + * @return This builder. + */ + fun protocol(protocol: Protocol) = apply { + this.protocol = protocol + } + + /** + * Sets the HTTP status code. + * + * @param code The HTTP status code. + * @return This builder. + * @throws IllegalArgumentException If [code] is negative. + */ + fun code(code: Int) = apply { + require(code >= 0) { "code must be >= 0" } + this.code = code + } + + /** + * Sets the HTTP reason phrase. + * + * @param message The reason phrase. + * @return This builder. + */ + fun message(message: String) = apply { + this.message = message + } + + /** + * Adds a header with the specified name and value. + * + * @param name The header name. + * @param value The header value. + * @return This builder. + * @throws IllegalArgumentException If [name] or [value] is invalid. + */ + fun addHeader(name: String, value: String) = apply { + headers.add(name, value) + } + + /** + * Adds a header with the specified name and values list. + * + * @param name The header name. + * @param values The header value. + * @return This builder. + * @throws IllegalArgumentException If [name] or [values] are invalid. + */ + fun addHeader(name: String, values: List) = apply { + headers.add(name, values) + } + + /** + * Sets a header with the specified name and value, replacing any existing values. + * + * @param name The header name. + * @param value The header value. + * @return This builder. + * @throws IllegalArgumentException If [name] or [value] is invalid. + */ + fun header(name: String, value: String) = apply { + headers.set(name, value) + } + + /** + * Sets a header with the specified name and values list, replacing any existing values. + * + * @param name The header name. + * @param values The header values list. + * @return This builder. + * @throws IllegalArgumentException If [name] or [values] are invalid. + */ + fun header(name: String, values: List) = apply { + headers.set(name, values) + } + + /** + * Removes all headers with the specified name. + * + * @param name The header name. + * @return This builder. + */ + fun removeHeader(name: String) = apply { + headers.remove(name) + } + + /** + * Sets the response headers. + * + * @param headers The response headers. + * @return This builder. + */ + fun headers(headers: Headers) = apply { + this.headers = headers.newBuilder() + } + + /** + * Sets the response body. + * + * @param body The response body, or null if none. + * @return This builder. + */ + fun body(body: ResponseBody?) = apply { + this.body = body + } + + /** + * Builds the [Response]. + * + * @return The built response. + * @throws IllegalStateException If required fields are missing. + */ + fun build(): Response { + val request = this.request ?: throw IllegalStateException("request is required") + val protocol = this.protocol ?: throw IllegalStateException("protocol is required") + val code = this.code.takeIf { it >= 0 } ?: throw IllegalStateException("code is required") + val message = this.message ?: throw IllegalStateException("message is required") + + return Response( + request = request, + protocol = protocol, + code = code, + message = message, + headers = headers.build(), + body = body + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ResponseBody.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ResponseBody.kt new file mode 100644 index 00000000..3d17e03c --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ResponseBody.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import java.io.Closeable +import java.io.InputStream +import okio.BufferedSource +import okio.IOException +import okio.buffer +import okio.source + +/** + * Represents the body of an HTTP response. + * + * This class is immutable but not thread-safe. + */ +abstract class ResponseBody : Closeable { + + /** + * Returns the media type of the response body, or null if unknown. + */ + abstract fun contentType(): MediaType? + + /** + * Returns the content length, or -1 if unknown. + */ + abstract fun contentLength(): Long + + /** + * Returns a [BufferedSource] to read the response body. + * + * Note: The source can be read only once. Multiple calls will return the same source. + * + * @return The buffered source. + */ + abstract fun source(): BufferedSource + + /** + * Closes the response body and releases any resources. + * + * @throws IOException If an I/O error occurs. + */ + @Throws(IOException::class) + override fun close() { + source().close() + } + + companion object { + /** + * Creates a new response body from an [InputStream] and [mediaType]. + * + * @param inputStream The input stream to read from. + * @param contentLength The length of the content, or -1 if unknown. + * @param mediaType The media type, or null if unknown. + * @return A new [ResponseBody] instance. + */ + fun create( + inputStream: InputStream, + mediaType: MediaType? = null, + contentLength: Long = -1L + ): ResponseBody { + return object : ResponseBody() { + private val source = inputStream.source().buffer() + + override fun contentType(): MediaType? = mediaType + + override fun contentLength(): Long = contentLength + + override fun source(): BufferedSource = source + } + } + + /** + * Creates a new response body from a [BufferedSource] and [mediaType]. + * + * @param source The buffered source to read from. + * @param contentLength The length of the content, or -1 if unknown. + * @param mediaType The media type, or null if unknown. + * @return A new [ResponseBody] instance. + */ + fun create(source: BufferedSource, mediaType: MediaType? = null, contentLength: Long = -1L): ResponseBody { + return object : ResponseBody() { + override fun contentType(): MediaType? = mediaType + + override fun contentLength(): Long = contentLength + + override fun source(): BufferedSource = source + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Status.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Status.kt new file mode 100644 index 00000000..c495de3d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Status.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + + +enum class Status(val code: Int) { + // Informational responses (100–199) + CONTINUE(100), + SWITCHING_PROTOCOLS(101), + PROCESSING(102), + EARLY_HINTS(103), + + // Successful responses (200–299) + OK(200), + CREATED(201), + ACCEPTED(202), + NON_AUTHORITATIVE_INFORMATION(203), + NO_CONTENT(204), + RESET_CONTENT(205), + PARTIAL_CONTENT(206), + MULTI_STATUS(207), + ALREADY_REPORTED(208), + IM_USED(226), + + // Redirection messages (300–399) + MULTIPLE_CHOICES(300), + MOVED_PERMANENTLY(301), + FOUND(302), + SEE_OTHER(303), + NOT_MODIFIED(304), + USE_PROXY(305), + TEMPORARY_REDIRECT(307), + PERMANENT_REDIRECT(308), + + // Client error responses (400–499) + BAD_REQUEST(400), + UNAUTHORIZED(401), + PAYMENT_REQUIRED(402), + FORBIDDEN(403), + NOT_FOUND(404), + METHOD_NOT_ALLOWED(405), + NOT_ACCEPTABLE(406), + PROXY_AUTHENTICATION_REQUIRED(407), + REQUEST_TIMEOUT(408), + CONFLICT(409), + GONE(410), + LENGTH_REQUIRED(411), + PRECONDITION_FAILED(412), + PAYLOAD_TOO_LARGE(413), + URI_TOO_LONG(414), + UNSUPPORTED_MEDIA_TYPE(415), + RANGE_NOT_SATISFIABLE(416), + EXPECTATION_FAILED(417), + IM_A_TEAPOT(418), + MISDIRECTED_REQUEST(421), + UNPROCESSABLE_ENTITY(422), + LOCKED(423), + FAILED_DEPENDENCY(424), + TOO_EARLY(425), + UPGRADE_REQUIRED(426), + PRECONDITION_REQUIRED(428), + TOO_MANY_REQUESTS(429), + REQUEST_HEADER_FIELDS_TOO_LARGE(431), + UNAVAILABLE_FOR_LEGAL_REASONS(451), + + // Server error responses (500–599) + INTERNAL_SERVER_ERROR(500), + NOT_IMPLEMENTED(501), + BAD_GATEWAY(502), + SERVICE_UNAVAILABLE(503), + GATEWAY_TIMEOUT(504), + HTTP_VERSION_NOT_SUPPORTED(505), + VARIANT_ALSO_NEGOTIATES(506), + INSUFFICIENT_STORAGE(507), + LOOP_DETECTED(508), + NOT_EXTENDED(510), + NETWORK_AUTHENTICATION_REQUIRED(511), + + // Non-standard status codes (e.g., Apache) + THIS_IS_FINE(218); // Non-standard code, used by some Apache modules + + companion object { + + @Throws(IllegalArgumentException::class) + fun fromCode(code: Int): Status { + entries.find { it.code == code }?.let { + return it + } + + throw IllegalArgumentException("Invalid status code: $code") + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Url.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Url.kt new file mode 100644 index 00000000..6fc313a1 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Url.kt @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.http + +import com.expediagroup.sdk.core2.http.Url.Companion.parse +import java.net.IDN +import java.net.InetAddress +import java.net.MalformedURLException +import java.net.URI +import java.net.URISyntaxException +import java.net.URL +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.Locale + +/** + * Represents an immutable URL with proper encoding and parsing. + * + * The [Url] class provides functionality for creating, validating, and manipulating URLs. It ensures that + * URLs are correctly encoded, parsed, and represented as standardized strings or objects like [URI] and [URL]. + * + * Instances of [Url] are immutable and must be created using the [Builder] or the [parse] method. + */ +class Url private constructor( + private val scheme: String, + private val userInfo: String?, + private val host: String, + private val port: Int, + private val encodedPath: String, + private val encodedQuery: String?, + private val fragment: String? +) { + + /** + * Returns the URL as a properly formatted string. + * The string representation includes the scheme, user info, host, port, path, query, and fragment. + */ + override fun toString(): String = buildString { + append(scheme) + append("://") + if (userInfo != null) { + append(userInfo) + append("@") + } + append(hostToString(host)) + if (port != defaultPort(scheme)) { + append(":") + append(port) + } + append(encodedPath) + if (encodedQuery != null) { + append("?") + append(encodedQuery) + } + if (fragment != null) { + append("#") + append(fragment) + } + } + + /** + * Converts the URL to a [URI] object. + * + * @return A [URI] representation of the URL. + * @throws URISyntaxException + */ + @Throws(URISyntaxException::class) + fun toUri(): URI = URI( + scheme, + userInfo, + host, + if (port != defaultPort(scheme)) port else -1, + encodedPath, + encodedQuery, + fragment + ) + + /** + * Converts the URL to a [URL] object. + * + * @return A [URL] representation of the URL. + * @throws MalformedURLException + */ + @Throws(MalformedURLException::class) + fun toUrl(): URL = toUri().toURL() + + /** + * Builder for creating and constructing [Url] instances. + * + * The [Builder] class provides a fluent API for incrementally specifying the components of a URL, such as + * the scheme, host, port, path segments, query parameters, and fragment. It ensures that all components + * are properly validated and encoded. + */ + class Builder { + private var scheme: String? = null + private var userInfo: String? = null + private var host: String? = null + private var port: Int? = null + private val pathSegments: MutableList = mutableListOf() + private val queryParameters: MutableList> = mutableListOf() + private var fragment: String? = null + + /** + * Sets the URL scheme (e.g., "http", "https"). + * + * @param scheme The scheme to use for the URL. + * @return The [Builder] instance for chaining. + * @throws IllegalArgumentException If the scheme is invalid. + */ + fun scheme(scheme: String) = apply { + require(scheme.matches(Regex("^[a-zA-Z][a-zA-Z0-9+\\-.]*$"))) { + "Invalid URL scheme: $scheme" + } + this.scheme = scheme.lowercase(Locale.US) + } + + /** + * Sets the user info (e.g., "user:password"). + * + * @param userInfo The user info to include in the URL. + * @return The [Builder] instance for chaining. + */ + fun userInfo(userInfo: String) = apply { + this.userInfo = userInfo + } + + /** + * Sets the host of the URL. + * + * @param host The hostname or IP address. + * @return The [Builder] instance for chaining. + * @throws IllegalArgumentException If the host is invalid. + */ + fun host(host: String) = apply { + require(isValidHost(host)) { "Invalid host: $host" } + this.host = host + } + + /** + * Sets the port of the URL. + * + * @param port The port number. + * @return The [Builder] instance for chaining. + * @throws IllegalArgumentException If the port is out of range (1–65535). + */ + fun port(port: Int) = apply { + require(port in 1..65535) { "Invalid port: $port" } + this.port = port + } + + /** + * Adds a path segment to the URL. + * + * @param segment The path segment to add. + * @return The [Builder] instance for chaining. + */ + fun addPathSegment(segment: String) = apply { + pathSegments.add(encodePathSegment(segment)) + } + + /** + * Adds a query parameter to the URL. + * + * @param name The query parameter name. + * @param value The query parameter value. + * @return The [Builder] instance for chaining. + */ + fun addQueryParameter(name: String, value: String) = apply { + queryParameters.add(encodeQueryParameter(name) to encodeQueryParameter(value)) + } + + /** + * Sets the fragment of the URL. + * + * @param fragment The fragment identifier. + * @return The [Builder] instance for chaining. + */ + fun fragment(fragment: String) = apply { + this.fragment = encodeFragment(fragment) + } + + /** + * Builds and returns a new [Url] instance. + * + * @return The constructed [Url]. + * @throws IllegalStateException If required components (e.g., scheme or host) are missing. + */ + fun build(): Url { + val scheme = this.scheme ?: throw IllegalStateException("Scheme is required.") + val host = this.host ?: throw IllegalStateException("Host is required.") + val port = this.port ?: defaultPort(scheme) + val encodedPath = buildEncodedPath() + val encodedQuery = buildEncodedQuery() + return Url( + scheme, + userInfo, + host, + port, + encodedPath, + encodedQuery, + fragment + ) + } + + private fun buildEncodedPath(): String { + return if (pathSegments.isEmpty()) { + "/" + } else { + pathSegments.joinToString("/", prefix = "/") + } + } + + private fun buildEncodedQuery(): String? { + return if (queryParameters.isEmpty()) { + null + } else { + queryParameters.joinToString("&") { (name, value) -> + if (value != null) "$name=$value" else name + } + } + } + + private fun encodePathSegment(segment: String): String { + return URLEncoder.encode(segment, StandardCharsets.UTF_8.name()) + .replace("+", "%20") + .replace("%2F", "/") + } + + private fun encodeQueryParameter(value: String): String { + return value.let { + URLEncoder.encode(it, StandardCharsets.UTF_8.name()) + .replace("+", "%20") + } + } + + private fun encodeFragment(fragment: String): String { + return URLEncoder.encode(fragment, StandardCharsets.UTF_8.name()) + .replace("+", "%20") + } + + private fun isValidHost(host: String): Boolean { + return try { + val idnHost = IDN.toASCII(host) + InetAddress.getByName(idnHost) + true + } catch (e: Exception) { + false + } + } + } + + companion object { + /** + * Parses a URL string into an [Url] instance. + * + * This method parses the given string, extracting and validating its components (e.g., scheme, host, port, path). + * + * @param url The URL string to parse. + * @return A [Url] instance representing the parsed URL, or `null` if the URL is invalid. + * @throws URISyntaxException If the URL cannot be parsed due to syntax issues. + */ + @Throws(URISyntaxException::class) + fun parse(url: String): Url? { + val uri = URI(url) + val scheme = uri.scheme ?: return null + val host = uri.host ?: return null + val port = if (uri.port != -1) uri.port else defaultPort(scheme) + val userInfo = uri.userInfo + val encodedPath = if (uri.rawPath != null && uri.rawPath.isNotEmpty()) uri.rawPath else "/" + val encodedQuery = uri.rawQuery + val fragment = uri.rawFragment + + return Url( + scheme = scheme, + userInfo = userInfo, + host = host, + port = port, + encodedPath = encodedPath, + encodedQuery = encodedQuery, + fragment = fragment + ) + } + + /** + * Returns the default port for a given scheme. + * + * @param scheme The URL scheme (e.g., "http", "https"). + * @return The default port for the scheme, or -1 if unknown. + */ + private fun defaultPort(scheme: String): Int { + return when (scheme.lowercase(Locale.US)) { + "http" -> 80 + "https" -> 443 + else -> -1 + } + } + + /** + * Converts the host string to its proper string representation. + * + * @param host The hostname or IP address. + * @return The string representation of the host, with IPv6 addresses enclosed in brackets. + */ + private fun hostToString(host: String): String { + return if (host.contains(":") && !host.startsWith("[")) { + "[$host]" // Enclose IPv6 addresses in brackets + } else { + host + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/Interceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/Interceptor.kt new file mode 100644 index 00000000..0974493d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/Interceptor.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.interceptor + +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import java.io.IOException + +/** + * Represents an interceptor for modifying or augmenting HTTP requests and responses within the SDK. + * + * An `Interceptor` allows pre-processing of requests and post-processing of responses as they pass + * through the HTTP execution pipeline. This can be used for various purposes such as logging, authentication, + * retry logic, or adding custom headers. + */ +interface Interceptor { + /** + * Intercepts an HTTP request and optionally modifies it or processes its corresponding response. + * + * Implementations of this method can inspect and modify the request before it is sent, or inspect + * and modify the response after it is received. The request is forwarded to the next interceptor in the chain + * by calling [Chain.proceed]. + * + * @param chain The [Chain] containing the request to process and the logic to continue the chain. + * @return The [Response] resulting from the processed request. + * @throws IOException If an I/O error occurs during request execution or interception. + */ + @Throws(IOException::class) + fun intercept(chain: Chain): Response + + /** + * Represents a chain of interceptors and the ability to proceed with an HTTP request. + * + * Each interceptor in the chain can call [proceed] to pass the request to the next interceptor or + * handle the request and response directly. + */ + interface Chain { + /** + * Retrieves the current HTTP request. + * + * @return The [Request] that is currently being processed. + */ + fun request(): Request + + /** + * Proceeds with the HTTP request, invoking the next interceptor in the chain or the final request execution. + * + * Interceptors use this method to pass the request down the chain. The returned response + * is the result of either the next interceptor or the HTTP client execution. + * + * @param request The [Request] to proceed with. + * @return The [Response] resulting from the request execution. + * @throws IOException If an I/O error occurs during request execution. + */ + @Throws(IOException::class) + fun proceed(request: Request): Response + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/InterceptorsChainExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/InterceptorsChainExecutor.kt new file mode 100644 index 00000000..323e0296 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/interceptor/InterceptorsChainExecutor.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.interceptor + +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import java.io.IOException + +/** + * Executes a chain of [Interceptor] instances, ensuring sequential processing of HTTP requests and responses. + * + * The `InterceptorChainExecutor` is responsible for managing the flow of a request through a list of + * interceptors, ultimately delegating to the [Transport] for request execution when all interceptors + * have been processed. + * + * This class implements the [Interceptor.Chain] interface, providing methods for accessing the current + * request and proceeding to the next interceptor or the final request execution. + * + * @param interceptors The list of [Interceptor] instances to process. + * @param index The current position in the interceptor chain. Defaults to `0` for the first interceptor. + * @param request The [Request] being processed. + * @param transport The [Transport] responsible for executing the final HTTP request after all interceptors. + */ +class InterceptorsChainExecutor( + private val transport: Transport, + private val interceptors: List, + private var index: Int = 0, + private var request: Request +) : Interceptor.Chain { + + /** + * Retrieves the current [Request] being processed by the chain. + * + * @return The current [Request]. + */ + override fun request(): Request = request + + /** + * Proceeds with the HTTP request by invoking the next [Interceptor] in the chain or + * executing the final request through the [Transport] if all interceptors have been processed. + * + * Each interceptor in the chain can modify the request or response as needed. If this is the last + * interceptor, the request is passed to the [Transport] for actual execution. + * + * @param request The [Request] to proceed with. + * @return The [Response] resulting from the next interceptor or the final HTTP client execution. + * @throws IOException If an I/O error occurs during request execution. + */ + @Throws(IOException::class) + override fun proceed(request: Request): Response { + this.request = request + + if (index >= interceptors.size) { + return transport.execute(request) + } + + val interceptor = interceptors[index++] + return interceptor.intercept(this) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt new file mode 100644 index 00000000..2da6151b --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt @@ -0,0 +1,103 @@ +package com.expediagroup.sdk.core2.logging + +import com.expediagroup.sdk.core2.http.RequestBody +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import com.expediagroup.sdk.core2.interceptor.Interceptor +import com.expediagroup.sdk.core2.logging.common.LoggerDecorator +import java.io.IOException +import java.nio.charset.Charset +import okio.Buffer +import okio.BufferedSource +import org.slf4j.LoggerFactory + +/** + * An interceptor that logs HTTP requests and responses. + * + * @param maxBodyLogSize The maximum size of the request/response body to log. Defaults to 1MB. + */ +class LoggingInterceptor( + private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE +) : Interceptor { + private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java)) + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val startTime = System.currentTimeMillis() + + logRequest(request) + val response = chain.proceed(request) + logResponse(response, System.currentTimeMillis() - startTime) + + return response + } + + private fun logRequest(request: Request) { + try { + buildLogMessage { + append(">>> HTTP ${request.method} ${request.url}\n") + append("\nHeaders:\n${request.headers}") // TODO: Mask sensitive headers + + request.body?.let { + appendBody(">>>", it.peekContent(maxBodyLogSize, it.contentType()?.charset), it.contentLength()) + } + }.also { + logger.info(it) + } + } catch (e: Exception) { + logger.warn("Failed to log request: ${e.message}", e) + } + } + + private fun logResponse(response: Response, durationMs: Long) { + try { + buildLogMessage { + append("<<< HTTP ${response.code} (${durationMs}ms) ${response.request.url}\n") + append("\nHeaders:\n${response.headers}") // TODO: Mask sensitive headers + + response.body?.let { + appendBody( + "<<<", + it.source().peekContent(maxBodyLogSize, it.contentType()?.charset), + it.contentLength() + ) + } + }.also { + logger.info(it) + } + } catch (e: Exception) { + logger.warn("Failed to log response: ${e.message}", e) + } + } + + private inline fun buildLogMessage(block: StringBuilder.() -> Unit): String = + StringBuilder().apply(block).toString() + + private fun StringBuilder.appendBody(prefix: String, content: String, contentLength: Long) { + if (content.isNotEmpty()) { + append("\n$prefix Body: $content") + if (contentLength > maxBodyLogSize) { + append("\n$prefix Body truncated, showing $maxBodyLogSize/$contentLength bytes") + } + } + } + + private fun RequestBody.peekContent(maxSize: Long, charset: Charset?): String { + val buffer = Buffer() + writeTo(buffer) + val bytesToRead = minOf(maxSize, buffer.size) + return buffer.copy().readString(bytesToRead, charset ?: Charsets.UTF_8) + } + + private fun BufferedSource.peekContent(maxSize: Long, charset: Charset?): String { + val buffer = Buffer() + val bytesToRead = minOf(maxSize, buffer.size) + buffer.write(this.peek(), bytesToRead) + return buffer.copy().readString(charset ?: Charsets.UTF_8) + } + + companion object { + private const val DEFAULT_MAX_BODY_SIZE = 1024L * 1024L // 1MB + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt new file mode 100644 index 00000000..c4a1c676 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt @@ -0,0 +1,19 @@ +package com.expediagroup.sdk.core2.logging.common + +object Constant { + const val NEWLINE = "\n" + const val COMMA_SPACE = ", " + const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" + const val REQUEST_HEADERS: String = "Request Headers:" + const val REQUEST_BODY: String = "Request Body:" + const val RESPONSE_HEADERS: String = "Response Headers:" + const val RESPONSE_BODY: String = "Response Body:" + const val EMPTY_OR_UNKNOWN_RESPONSE_BODY: String = "Empty response body or unknown content length" + const val BODY_CONTENT_TYPE_NOT_SUPPORTED = "Content type %s not supported for logging" + const val RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE = "Response too large to be logged whole..." + const val LOGGING_PREFIX = "[ExpediaGroupSDK]" + const val TOKEN_RENEWAL_IN_PROGRESS = "Renewing token" + const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" + const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" + const val OMITTED = "<-- omitted -->" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt new file mode 100644 index 00000000..b83b5355 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt @@ -0,0 +1,16 @@ +package com.expediagroup.sdk.core2.logging.common + +/** + * Enumeration representing the different tags available for log messages. + * + * @property tag The string representation of the log message tag. + */ +enum class LogMessageTag(val tag: String) { + PROGRESSING("PROGRESSING"), + SUCCESS("SUCCESS"), + ERROR("ERROR"), + INCOMING("INCOMING"), + OUTGOING("OUTGOING"); + + override fun toString(): String = tag +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt new file mode 100644 index 00000000..90fa0831 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt @@ -0,0 +1,21 @@ +package com.expediagroup.sdk.core2.logging.common + +import com.expediagroup.sdk.core2.http.ContentType + +/** + * A list of MIME types representing content types that are deemed loggable. + * This collection is used to determine whether the content of HTTP requests or responses + * can be logged based on their MIME types. + */ +val LOGGABLE_CONTENT_TYPES = buildList { + add(ContentType.APPLICATION_ATOM_XML.mimeType) + add(ContentType.APPLICATION_JSON.mimeType) + add(ContentType.APPLICATION_XML.mimeType) + add(ContentType.APPLICATION_FORM_URLENCODED.mimeType) + add(ContentType.APPLICATION_SOAP_XML.mimeType) + add(ContentType.APPLICATION_XHTML_XML.mimeType) + add(ContentType.TEXT_PLAIN.mimeType) + add(ContentType.TEXT_HTML.mimeType) + add(ContentType.TEXT_XML.mimeType) + add(ContentType.DEFAULT_TEXT.mimeType) +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt new file mode 100644 index 00000000..c6fa8bc4 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.logging.common + +import com.expediagroup.sdk.core2.logging.masking.maskLogs +import org.slf4j.Logger + +/** + * The ExpediaGroupLogger class is a decorator for the Logger interface that enhances log messages + * with additional formatting and tagging functionality. + * + * @param logger The underlying Logger instance to delegate logging actions to. + */ +internal class LoggerDecorator(private val logger: Logger) : Logger by logger { + override fun info(msg: String) = logger.info(decorate(msg)) + + fun info(msg: String, vararg tags: LogMessageTag) = logger.info(decorate(msg, tags.toSet())) + + override fun warn(msg: String) = logger.warn(decorate(msg)) + + fun warn(msg: String, vararg tags: LogMessageTag) = logger.warn(decorate(msg, tags.toSet())) + + override fun debug(msg: String) = logger.debug(decorate(msg)) + + fun debug(msg: String, vararg tags: LogMessageTag) = logger.debug(decorate(msg, tags.toSet())) + + override fun error(msg: String) = logger.error(decorate(msg)) + + fun error(msg: String, vararg tags: LogMessageTag) = logger.error(decorate(msg, tags.toSet())) + + /** + * Normalizes a log message by applying specific formatting and tagging. + * + * @param msg The log message to normalize. + * @param tags A set of tags to include in the normalized message. + * @return A formatted and tagged log message. + */ + private fun normalize(msg: String, tags: Set = emptySet()): String = + buildList { + maskLogs(msg) + .trim() + .split(Constant.NEWLINE) + .forEach { line -> + tags.joinToString(Constant.COMMA_SPACE).let { tagsPrefix -> + if (tagsPrefix.isNotBlank()) "[$tagsPrefix]" else "" + }.also { + add("${Constant.DOUBLE_RIGHT_ANGLE_BRACKETS} $it $line".trim()) + } + } + }.joinToString(Constant.NEWLINE) + + /** + * Decorates a log message by prefixing it with a logging prefix and normalizing its format using provided tags. + * + * @param msg The message to be decorated. + * @param tags A set of tags to include in the decorated message. Defaults to an empty set. + * @return The decorated log message with the logging prefix and normalized format. + */ + private fun decorate(msg: String, tags: Set = emptySet()): String = + "${Constant.LOGGING_PREFIX} ${normalize(msg, tags)}".trim() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt new file mode 100644 index 00000000..1be96df7 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt @@ -0,0 +1,19 @@ +package com.expediagroup.sdk.core2.logging.masking + +import com.ebay.ejmask.core.BaseFilter + +/** + * A filter class that extends the BaseFilter to apply masking on specific JSON fields using + * `ExpediaGroupJsonFieldPatternBuilder` for pattern building. + * + * This filter helps in masking sensitive JSON fields by replacing them with a predefined pattern. + * + * @constructor + * Initializes ExpediaGroupJsonFieldFilter with the specified fields to be masked. + * + * @param maskedFields An array of strings representing the names of the fields to be masked. + */ +internal class JsonFieldFilter(maskedFields: Array) : BaseFilter( + JsonFieldPatternBuilder::class.java, + *maskedFields +) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt new file mode 100644 index 00000000..cd31a4d9 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt @@ -0,0 +1,17 @@ +package com.expediagroup.sdk.core2.logging.masking + +import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder +import com.expediagroup.sdk.core2.logging.common.Constant.OMITTED + +/** + * A builder class for creating JSON field replacement patterns specifically for Expedia Group. + * + * This class extends the `JsonFieldPatternBuilder` and provides an implementation for building + * replacement patterns for JSON field masking. + * + * The replacement pattern format generated by this builder is structured to conceal sensitive + * data while keeping a specified number of characters visible. + */ +internal class JsonFieldPatternBuilder : JsonFieldPatternBuilder() { + override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = "\"$1$2$OMITTED\"" +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt new file mode 100644 index 00000000..3fc82ff6 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt @@ -0,0 +1,100 @@ +package com.expediagroup.sdk.core2.logging.masking + +import com.ebay.ejmask.core.BaseFilter +import com.ebay.ejmask.core.EJMask +import com.ebay.ejmask.core.EJMaskInitializer +import com.ebay.ejmask.core.util.LoggerUtil + +/** + * Masks sensitive information within the provided log string. + * + * @param logs The log string that may contain sensitive information requiring masking. + * @return A new log string with sensitive information masked. + */ +fun maskLogs(logs: String): String { + return MaskLogs.execute(logs) +} + +/** + * Configures log masking by adding specified fields to the mask list. + * + * This function integrates with the log masking system to include additional fields + * that should be masked in the logs. The fields provided in the parameter are added + * to the set of fields that will have their values masked when logs are generated. + * + * @param fields The set of field names that need to be masked in the logs. + */ +fun configureLogMasking(fields: Set) { + MaskLogs.addFields(fields) +} + +/** + * Checks if a specified field is among the fields that should be masked. + * + * @param field The name of the field to check. + * @return `true` if the field should be masked, `false` otherwise. + */ +fun isMaskedField(field: String): Boolean { + return MaskLogs.maskedFields.contains(field) +} + +/** + * A utility class for masking sensitive information in log strings. + * + * The `MaskLogs` class is designed to replace sensitive information within logs with masked values. + * The class implements the `Function1` interface, enabling it to be invoked with a log string + * to produce a masked version of the string. + * + * The masking process relies on predefined filters that determine which fields within the log + * should be masked. Filters can be added and configured using the companion object's methods. + */ +private class MaskLogs : (String) -> String { + companion object { + @JvmStatic + val filters: MutableList = mutableListOf() + + val maskedFields: MutableSet = mutableSetOf() + + @JvmStatic + val INSTANCE = MaskLogs() + + /** + * Executes the masking process on the provided log string. + * + * @param logs The log string that may contain sensitive information requiring masking. + */ + @JvmStatic + fun execute(logs: String) = INSTANCE(logs) + + /** + * Adds specified fields to the list of fields to be masked in logs. + * + * The fields provided in the parameter are added to the internal set of fields + * and corresponding filters are created and added to the filter list. These filters + * are then integrated into the masking system to ensure the specified fields are + * masked in any logs they appear in. + * + * @param fields The set of field names that need to be masked in the logs. + */ + @JvmStatic + fun addFields(fields: Set) { + maskedFields.addAll(fields) + filters.add(JsonFieldFilter(fields.toTypedArray())) + filters.forEach { EJMaskInitializer.addFilter(it) } + } + } + + init { + LoggerUtil.register { _, _, _ -> /* disable logging */ } + } + + /** + * Masks the given text using the EJMask utility. + * + * @param text The input text that needs to be masked. + * @return The masked version of the input text. + */ + override fun invoke(text: String): String { + return EJMask.mask(text) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt new file mode 100644 index 00000000..fe045f80 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.okhttp + +import java.time.Duration +import okhttp3.OkHttpClient + +internal object BaseOkHttpClient { + @Volatile + private var instance: OkHttpClient? = null + + fun getInstance(): OkHttpClient { + return instance ?: synchronized(this) { + instance ?: OkHttpClient().also { instance = it } + } + } + + fun getConfiguredInstance(configuration: OkHttpClientConfiguration): OkHttpClient = getInstance() + .newBuilder() + .apply { + configuration.callTimeout?.let { + callTimeout(Duration.ofMillis(it.toLong())) + } + configuration.connectTimeout?.let { + connectTimeout(Duration.ofMillis(it.toLong())) + } + configuration.readTimeout?.let { + readTimeout(Duration.ofMillis(it.toLong())) + } + configuration.writeTimeout?.let { + writeTimeout(Duration.ofMillis(it.toLong())) + } + configuration.connectionPool?.let { + connectionPool(it) + } + configuration.retryOnConnectionFailure?.let { + retryOnConnectionFailure(it) + } + configuration.interceptors?.forEach { + addInterceptor(it) + } + configuration.networkInterceptors?.forEach { + addNetworkInterceptor(it) + } + }.build() +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt new file mode 100644 index 00000000..9a88f1cd --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.okhttp + +import okhttp3.ConnectionPool +import okhttp3.Interceptor + +data class OkHttpClientConfiguration( + val interceptors: List? = null, + val networkInterceptors: List? = null, + val connectionPool: ConnectionPool? = null, + val retryOnConnectionFailure: Boolean? = null, + val callTimeout: Int? = null, + val connectTimeout: Int? = null, + val readTimeout: Int? = null, + val writeTimeout: Int? = null, +) { + open class Builder { + private var interceptors: List? = null + private var networkInterceptors: List? = null + private var connectionPool: ConnectionPool? = null + private var retryOnConnectionFailure: Boolean? = null + private var callTimeout: Int? = null + private var connectTimeout: Int? = null + private var readTimeout: Int? = null + private var writeTimeout: Int? = null + + fun interceptors(interceptors: List) = apply { + this.interceptors = interceptors + } + + fun networkInterceptors(networkInterceptors: List) = apply { + this.networkInterceptors = networkInterceptors + } + + fun connectionPool(connectionPool: ConnectionPool) = apply { + this.connectionPool = connectionPool + } + + fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean) = apply { + this.retryOnConnectionFailure = retryOnConnectionFailure + } + + fun callTimeout(callTimeout: Int) = apply { + this.callTimeout = callTimeout + } + + fun connectTimeout(connectTimeout: Int) = apply { + this.connectTimeout = connectTimeout + } + + fun readTimeout(readTimeout: Int) = apply { + this.readTimeout = readTimeout + } + + fun writeTimeout(writeTimeout: Int) = apply { + this.writeTimeout = writeTimeout + } + + fun build(): OkHttpClientConfiguration { + return OkHttpClientConfiguration( + interceptors, + networkInterceptors, + connectionPool, + retryOnConnectionFailure, + callTimeout, + connectTimeout, + readTimeout, + writeTimeout + ) + } + } + + companion object { + @JvmStatic + fun builder() = Builder() + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt new file mode 100644 index 00000000..892e1ed3 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 Expedia, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.expediagroup.sdk.core2.okhttp + +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.http.ResponseBody.Companion.create +import com.expediagroup.sdk.core2.http.MediaType.Companion.parse +import com.expediagroup.sdk.core2.http.Protocol +import java.io.IOException +import okhttp3.Headers +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.Response +import okhttp3.ResponseBody +import okio.BufferedSink + +typealias OkHttpRequest = Request +typealias OkHttpRequestBuilder = Request.Builder +typealias OkHttpRequestBody = RequestBody +typealias OkHttpResponse = Response +typealias OkHttpResponseBody = ResponseBody +typealias OkHttpHeaders = Headers +typealias OkHttpHeadersBuilder = Headers.Builder + +typealias SdkRequest = com.expediagroup.sdk.core2.http.Request +typealias SdkRequestBody = com.expediagroup.sdk.core2.http.RequestBody +typealias SdkResponse = com.expediagroup.sdk.core2.http.Response +typealias SdkResponseBuilder = com.expediagroup.sdk.core2.http.Response.Builder +typealias SdkHeaders = com.expediagroup.sdk.core2.http.Headers +typealias SdkHeadersBuilder = com.expediagroup.sdk.core2.http.Headers.Builder + +class OkHttpTransport( + private val okHttpClient: OkHttpClient +) : Transport { + + @Throws(IOException::class) + override fun execute(request: SdkRequest): SdkResponse { + val okHttpRequest = toOkHttpRequest(request) + val okHttpResponse = okHttpClient.newCall(okHttpRequest).execute() + return toSdkHttpResponse(okHttpResponse, request) + } + + private fun toOkHttpRequest(sdkRequest: SdkRequest): OkHttpRequest { + return OkHttpRequestBuilder().apply { + url(sdkRequest.url.toUrl()) + headers(buildHeaders(sdkRequest.headers)) + method(sdkRequest.method, createRequestBody(sdkRequest.body)) + tag(sdkRequest.tags) + }.build() + } + + private fun buildHeaders(headers: SdkHeaders): OkHttpHeaders { + return OkHttpHeadersBuilder().apply { + headers.entries().forEach { (name, values) -> + values.forEach { value -> add(name, value) } + } + }.build() + } + + private fun createRequestBody(body: SdkRequestBody?): OkHttpRequestBody? { + return body?.let { + object : OkHttpRequestBody() { + override fun contentType() = it.contentType()?.toString()?.toMediaTypeOrNull() + override fun contentLength() = it.contentLength() + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) = it.writeTo(sink) + } + } + } + + private fun toSdkHttpResponse(okHttpResponse: OkHttpResponse, sdkRequest: SdkRequest): SdkResponse { + return SdkResponseBuilder().apply { + headers(buildSdkHeaders(okHttpResponse.headers)) + okHttpResponse.body?.let { buildResponseBody(it) }?.let { body(it) } + request(sdkRequest) + protocol(Protocol.valueOf(okHttpResponse.protocol.name)) + code(okHttpResponse.code) + message(okHttpResponse.message) + }.build() + } + + private fun buildSdkHeaders(headers: OkHttpHeaders): SdkHeaders { + return SdkHeadersBuilder().apply { + headers.toMultimap().forEach { (key, values) -> add(key, values) } + }.build() + } + + private fun buildResponseBody(body: OkHttpResponseBody) = body.run { + create( + source = source(), + contentLength = contentLength(), + mediaType = contentType()?.toString()?.let(::parse) + ) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt new file mode 100644 index 00000000..7b366bdf --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt @@ -0,0 +1,13 @@ +package com.expediagroup.sdk.graphql.common + +import com.apollographql.apollo.api.http.HttpRequest +import com.apollographql.apollo.api.http.HttpResponse +import com.apollographql.apollo.api.Error + +typealias ApolloHttpRequest = HttpRequest + +typealias ApolloHttpResponse = HttpResponse + +typealias ApolloHttpResponseBuilder = HttpResponse.Builder + +typealias ApolloError = Error diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt new file mode 100644 index 00000000..90a7862d --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt @@ -0,0 +1,125 @@ +package com.expediagroup.sdk.graphql.common + +import com.apollographql.apollo.api.http.HttpHeader +import com.apollographql.apollo.api.http.HttpMethod +import com.apollographql.apollo.exception.ApolloNetworkException +import com.apollographql.java.client.ApolloDisposable +import com.apollographql.java.client.network.http.HttpCallback +import com.apollographql.java.client.network.http.HttpEngine +import com.expediagroup.sdk.core2.client.RequestExecutor +import com.expediagroup.sdk.core2.http.MediaType +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.RequestBody +import com.expediagroup.sdk.core2.http.Response +import java.io.IOException +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import okio.BufferedSink + +class ApolloHttpEngine( + private val requestExecutor: RequestExecutor +) : HttpEngine { + private val activeRequests = ConcurrentHashMap() + private val isDisposed = AtomicBoolean(false) + + override fun execute(request: ApolloHttpRequest, callback: HttpCallback, disposable: ApolloDisposable) { + if (isDisposed.get()) { + callback.onFailure(ApolloNetworkException("HTTP engine has been disposed")) + return + } + + val requestId = UUID.randomUUID().toString() + activeRequests[requestId] = disposable + + try { + validateRequest(request) + val sdkResponse = requestExecutor.execute(buildSdkRequestFromApolloRequest(request)) + + if (!isDisposed.get()) { + callback.onResponse(buildApolloResponseFromSdkResponse(sdkResponse)) + } + } catch (e: Exception) { + callback.onFailure(ApolloNetworkException("Unexpected error occurred", e)) + } finally { + activeRequests.remove(requestId) + } + } + + override fun dispose() { + if (isDisposed.compareAndSet(false, true)) { + activeRequests.values.forEach { it.dispose() } + activeRequests.clear() + } + } + + private fun buildSdkRequestFromApolloRequest(apolloRequest: ApolloHttpRequest): Request { + return Request.Builder() + .url(apolloRequest.url) + .apply { + addSdkRequestHeadersFromApolloRequest(apolloRequest, this) + addSdkRequestBodyFromApolloRequest(apolloRequest, this) + } + .build() + } + + private fun buildApolloResponseFromSdkResponse(sdkHttpResponse: Response): ApolloHttpResponse { + return ApolloHttpResponseBuilder(sdkHttpResponse.code).apply { + addApolloResponseHeadersFromSdkResponse(sdkHttpResponse, this) + addApolloResponseBodyFromSdkResponse(sdkHttpResponse, this) + }.build() + } + + private fun addSdkRequestHeadersFromApolloRequest( + request: ApolloHttpRequest, + sdkRequestBuilder: Request.Builder + ) { + request.headers.forEach { apolloHeader -> + sdkRequestBuilder.addHeader(apolloHeader.name, apolloHeader.value) + } + sdkRequestBuilder.addHeader("Content-Type", "application/json") + } + + private fun addSdkRequestBodyFromApolloRequest(request: ApolloHttpRequest, sdkRequestBuilder: Request.Builder) { + if (request.method != HttpMethod.Post) { + throw UnsupportedOperationException("Only POST requests are supported for GraphQL") + } + + request.body?.let { requestBody -> + sdkRequestBuilder.method("POST", object : RequestBody() { + override fun contentType(): MediaType? = MediaType.parse(requestBody.contentType) + override fun contentLength(): Long = requestBody.contentLength + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) = requestBody.writeTo(sink) + }) + } + } + + private fun addApolloResponseHeadersFromSdkResponse( + sdkHttpResponse: Response, + apolloHttpResponseBuilder: ApolloHttpResponseBuilder + ) { + sdkHttpResponse.headers.names().mapNotNull { headerName -> + sdkHttpResponse.headers.get(headerName)?.let { headerValue -> + HttpHeader(name = headerName, value = headerValue) + } + }.let { + apolloHttpResponseBuilder.headers(it) + } + } + + private fun addApolloResponseBodyFromSdkResponse( + sdkHttpResponse: Response, + apolloHttpResponseBuilder: ApolloHttpResponseBuilder + ) { + sdkHttpResponse.body?.let { + apolloHttpResponseBuilder.body(it.source()) + } + } + + private fun validateRequest(request: ApolloHttpRequest) { + require(request.url.isNotBlank()) { "Request URL cannot be blank" } + require(request.method == HttpMethod.Post) { "Only POST requests are supported for GraphQL" } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt index 773cbf59..3086d60b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt @@ -21,18 +21,18 @@ import com.apollographql.apollo.api.Mutation import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.Query import com.apollographql.java.client.ApolloClient -import com.expediagroup.sdk.core.client.ApiClientApolloHttpEngine -import com.expediagroup.sdk.core.client.util.createApiClient -import com.expediagroup.sdk.core.configuration.FullClientConfiguration import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException -import com.expediagroup.sdk.graphql.extension.toSDKError +import com.expediagroup.sdk.core2.client.RequestExecutor import com.expediagroup.sdk.graphql.model.exception.NoDataException +import com.expediagroup.sdk.graphql.model.response.Error import com.expediagroup.sdk.graphql.model.response.RawResponse import java.util.concurrent.CompletableFuture + /** - * Default implementation of [GraphQLExecutor], responsible for executing GraphQL queries and mutations - * using Apollo Kotlin with a custom HTTP client. + * A streamlined implementation of [GraphQLExecutor] that handles GraphQL operations with robust + * error handling and clean response processing. This executor processes both queries and mutations + * while providing detailed error information when operations fail. * * This executor leverages the Apollo Client to perform requests and processes responses by capturing * the entire data structure and any errors in a [RawResponse], which can then be further processed or @@ -40,18 +40,17 @@ import java.util.concurrent.CompletableFuture * * By default - this implementation is used internally in all higher-level clients that extend [GraphQLClient] abstract class * - * @param configuration Configuration details required to set up the custom client and Apollo Client. + * @param requestExecutor used for HTTP request execution within the SDK + * @param serverUrl GraphQL server URL */ -internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : GraphQLExecutor() { - - private val engine = ApiClientApolloHttpEngine(createApiClient(configuration = configuration)) +internal class DefaultGraphQLExecutor(requestExecutor: RequestExecutor, serverUrl: String) : GraphQLExecutor() { /** * The Apollo Client used to execute GraphQL requests, configured with a custom HTTP client. */ override val apolloClient: ApolloClient = ApolloClient.Builder() - .serverUrl(configuration.getEndpoint()) - .httpEngine(engine) + .serverUrl(serverUrl) + .httpEngine(ApolloHttpEngine(requestExecutor)) .build() @@ -61,9 +60,10 @@ internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : * * @param query The GraphQL query to be executed. * @return A [CompletableFuture] with the full data structure and any errors from the server. - * @throws ExpediaGroupServiceException If an exception occurs during query execution. - * @throws NoDataException If the query completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during query execution. + * @throws [NoDataException] If the query completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) override fun executeAsync(query: Query): CompletableFuture> { return CompletableFuture>().also { apolloClient.query(query).enqueue { response -> processOperationResponse(response, it) } @@ -75,9 +75,10 @@ internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : * * @param query The GraphQL query to be executed. * @return A [RawResponse] with the full data structure and any errors from the server. - * @throws ExpediaGroupServiceException If an exception occurs during query execution. - * @throws NoDataException If the query completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during query execution. + * @throws [NoDataException] If the query completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) override fun execute(query: Query): RawResponse = executeAsync(query).get() /** @@ -86,9 +87,10 @@ internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : * * @param mutation The GraphQL mutation to be executed. * @return A [CompletableFuture] with the full data structure and any errors from the server. - * @throws ExpediaGroupServiceException If an exception occurs during mutation execution. - * @throws NoDataException If the mutation completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during mutation execution. + * @throws [NoDataException] If the mutation completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) override fun executeAsync(mutation: Mutation): CompletableFuture> { return CompletableFuture>().also { apolloClient.mutation(mutation).enqueue { response -> processOperationResponse(response, it) } @@ -100,9 +102,10 @@ internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : * * @param mutation The GraphQL mutation to be executed. * @return A [RawResponse] with the full data structure and any errors from the server. - * @throws ExpediaGroupServiceException If an exception occurs during mutation execution. - * @throws NoDataException If the mutation completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during mutation execution. + * @throws [NoDataException] If the mutation completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) override fun execute(mutation: Mutation): RawResponse = executeAsync(mutation).get() @@ -119,31 +122,25 @@ internal class DefaultGraphQLExecutor(configuration: FullClientConfiguration) : ) { try { when { - response.exception != null -> { - future.completeExceptionally( - ExpediaGroupServiceException( - message = response.exception?.message, - cause = response.exception - ) + response.exception != null -> future.completeExceptionally( + ExpediaGroupServiceException( + message = response.exception?.message, + cause = response.exception ) - } + ) - response.data != null && response.hasErrors() -> { - future.completeExceptionally( - NoDataException( - message = "No data received from the server", - errors = response.errors!!.map { it.toSDKError() }) - ) - } + response.data != null && response.hasErrors() -> future.completeExceptionally( + NoDataException( + message = "No data received from the server", + errors = response.errors!!.map { Error.fromApolloError(it) }) + ) - else -> { - future.complete( - RawResponse( - data = response.data!!, - errors = response.errors?.map { it.toSDKError() } - ) + else -> future.complete( + RawResponse( + data = response.data!!, + errors = response.errors?.map { Error.fromApolloError(it) } ) - } + ) } } catch (e: Exception) { future.completeExceptionally( diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt index f44c39f9..483c2c83 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLClient.kt @@ -16,6 +16,8 @@ package com.expediagroup.sdk.graphql.common +import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint + /** * Abstract base class for GraphQL clients that defines the core structure for executing GraphQL operations. * Classes extending `GraphQLClient` are expected to provide an implementation of the [GraphQLExecutor]. @@ -24,6 +26,13 @@ package com.expediagroup.sdk.graphql.common * while relying on the `graphQLExecutor` to perform the actual request handling. */ abstract class GraphQLClient { + + /** + * The API endpoint that the client is configured to communicate with. Includes the primary API endpoint + * and the authentication endpoint. + */ + protected abstract val apiEndpoint: ApiEndpoint + /** * The executor responsible for handling GraphQL operations. * Subclasses must provide a concrete implementation of this executor to define diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt index bdf31ed5..54756e54 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/GraphQLExecutor.kt @@ -16,10 +16,11 @@ package com.expediagroup.sdk.graphql.common -import com.apollographql.java.client.ApolloClient import com.apollographql.apollo.api.Mutation import com.apollographql.apollo.api.Query +import com.apollographql.java.client.ApolloClient import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException +import com.expediagroup.sdk.graphql.model.exception.NoDataException import com.expediagroup.sdk.graphql.model.response.RawResponse import java.util.concurrent.CompletableFuture @@ -44,9 +45,10 @@ abstract class GraphQLExecutor { * * @param query The GraphQL query to be executed. * @return A [RawResponse] containing the full data and any errors from the query response. - * @throws ExpediaGroupServiceException If an exception occurs during the execution of the query. - * @throws NoDataException If the query completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during the execution of the query. + * @throws [NoDataException] If the query completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) abstract fun execute(query: Query): RawResponse /** @@ -54,9 +56,10 @@ abstract class GraphQLExecutor { * * @param mutation The GraphQL mutation to be executed. * @return A [RawResponse] containing the full data and any errors from the mutation response. - * @throws ExpediaGroupServiceException If an exception occurs during the execution of the mutation. - * @throws NoDataException If the mutation completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during the execution of the mutation. + * @throws [NoDataException] If the mutation completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) abstract fun execute(mutation: Mutation): RawResponse /** @@ -64,20 +67,20 @@ abstract class GraphQLExecutor { * * @param query The GraphQL query to be executed. * @return A [CompletableFuture] containing the full data and any errors from the query response wrapped in [RawResponse]. - * @throws ExpediaGroupServiceException If an exception occurs during the execution of the query. - * @throws NoDataException If the query completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during the execution of the query. + * @throws [NoDataException] If the query completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) abstract fun executeAsync(query: Query): CompletableFuture> /** * Asynchronously executes a GraphQL mutation and returns the complete raw response in a [CompletableFuture]. * - * @param query The GraphQL mutation to be executed. + * @param mutation The GraphQL mutation to be executed. * @return A [CompletableFuture] containing the full data and any errors from the mutation response wrapped in [RawResponse]. - * @throws ExpediaGroupServiceException If an exception occurs during the execution of the mutation. - * @throws NoDataException If the mutation completes without data but includes errors. + * @throws [ExpediaGroupServiceException] If an exception occurs during the execution of the mutation. + * @throws [NoDataException] If the mutation completes without data but includes errors. */ + @Throws(NoDataException::class, ExpediaGroupServiceException::class) abstract fun executeAsync(mutation: Mutation): CompletableFuture> } - - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt index 208519c0..ba871d3c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/model/response/Error.kt @@ -16,6 +16,8 @@ package com.expediagroup.sdk.graphql.model.response +import com.expediagroup.sdk.graphql.common.ApolloError + /** * Represents an error returned from a GraphQL operation. * @@ -25,5 +27,14 @@ package com.expediagroup.sdk.graphql.model.response */ data class Error( val message: String, - val path: List?, -) + val path: List? +) { + companion object { + fun fromApolloError(apolloError: ApolloError): Error { + return Error( + message = apolloError.message, + path = apolloError.path?.map { it.toString() } + ) + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt new file mode 100644 index 00000000..c6266846 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt @@ -0,0 +1,49 @@ +package com.expediagroup.sdk.lodgingconnectivity.common + +import com.expediagroup.sdk.core2.authentication.bearer.BearerAuthenticationInterceptor +import com.expediagroup.sdk.core2.authentication.common.Credentials +import com.expediagroup.sdk.core2.client.RequestExecutor +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.http.Request +import com.expediagroup.sdk.core2.http.Response +import com.expediagroup.sdk.core2.interceptor.Interceptor +import com.expediagroup.sdk.core2.interceptor.InterceptorsChainExecutor +import com.expediagroup.sdk.core2.logging.LoggingInterceptor +import com.expediagroup.sdk.core2.okhttp.BaseOkHttpClient +import com.expediagroup.sdk.core2.okhttp.OkHttpTransport +import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration + +internal fun getHttpTransport(configuration: ClientConfiguration): Transport = when (configuration) { + is CustomClientConfiguration -> configuration.transport + is DefaultClientConfiguration -> OkHttpTransport(BaseOkHttpClient.getConfiguredInstance(configuration.buildOkHttpConfiguration())) +} + +class DefaultRequestExecutor( + configuration: ClientConfiguration, + apiEndpoint: ApiEndpoint, + private val transport: Transport = getHttpTransport(configuration) +) : RequestExecutor(transport) { + + override val interceptors: List = listOf( + LoggingInterceptor(), + BearerAuthenticationInterceptor( + transport = this.transport, + authUrl = apiEndpoint.authEndpoint, + credentials = Credentials(configuration.key, configuration.secret) + ) + ) + + override fun execute(request: Request): Response { + val chainExecutor = InterceptorsChainExecutor( + interceptors = interceptors, + request = request, + transport = this.transport + ) + + return chainExecutor.proceed(request) + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt index 9795e825..de1cd6f4 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -16,188 +16,179 @@ package com.expediagroup.sdk.lodgingconnectivity.configuration -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy -import com.expediagroup.sdk.core.configuration.ExpediaGroupDefaultClientConfiguration -import com.expediagroup.sdk.core.configuration.FullClientConfiguration +import com.expediagroup.sdk.core2.client.Transport +import com.expediagroup.sdk.core2.okhttp.OkHttpClientConfiguration +import okhttp3.ConnectionPool +import okhttp3.Interceptor -/** - * A configuration class that holds the necessary credentials and settings for API clients. - * - * This class is used to configure SDK clients by providing essential - * details such as API keys, environment, timeouts, and logging settings. - * - * It also provides a fluent `Builder` pattern for easy creation of configuration instances. - * - * @property key The API key used for authentication. - * @property secret The API secret used for authentication. - * @property environment The environment in which the API client will operate (e.g., production or test). - * @property requestTimeout The request timeout duration in milliseconds (optional). - * @property connectionTimeout The connection timeout duration in milliseconds (optional). - * @property socketTimeout The socket timeout duration in milliseconds (optional). - * @property maskedLoggingHeaders A set of HTTP headers whose values should be masked in logs (optional). - * @property maskedLoggingBodyFields A set of fields in the request body whose values should be masked in logs (optional). - */ -data class ClientConfiguration( - val key: String?, - val secret: String?, - val environment: ClientEnvironment?, - val requestTimeout: Long? = null, - val connectionTimeout: Long? = null, - val socketTimeout: Long? = null, - val maskedLoggingHeaders: Set? = null, - val maskedLoggingBodyFields: Set? = null, - val maxConnTotal: Int? = null, - val maxConnPerRoute: Int? = null + +sealed class ClientConfiguration( + open val key: String, + open val secret: String, + open val environment: ClientEnvironment? = null, ) { - /** - * A builder for creating `ClientConfiguration` instances. - */ + companion object { + @JvmStatic + fun builder(): DefaultClientConfiguration.Builder = DefaultClientConfiguration.Builder() + + @JvmStatic + fun builder(transport: Transport): CustomClientConfiguration.Builder = + CustomClientConfiguration.Builder(transport) + } +} + + +data class DefaultClientConfiguration( + override val key: String, + override val secret: String, + override val environment: ClientEnvironment? = null, + val interceptors: List? = null, + val networkInterceptors: List? = null, + val connectionPool: ConnectionPool? = null, + val retryOnConnectionFailure: Boolean? = null, + val callTimeout: Int? = null, + val connectTimeout: Int? = null, + val readTimeout: Int? = null, + val writeTimeout: Int? = null +) : ClientConfiguration(key, secret, environment) { + + fun buildOkHttpConfiguration() = OkHttpClientConfiguration( + interceptors = interceptors, + networkInterceptors = networkInterceptors, + connectionPool = connectionPool, + retryOnConnectionFailure = retryOnConnectionFailure, + callTimeout = callTimeout, + connectTimeout = connectTimeout, + readTimeout = readTimeout, + writeTimeout = writeTimeout + ) + class Builder { private var key: String? = null private var secret: String? = null private var environment: ClientEnvironment? = null - private var requestTimeout: Long? = null - private var connectionTimeout: Long? = null - private var socketTimeout: Long? = null - private var maskedLoggingHeaders: Set? = null - private var maskedLoggingBodyFields: Set? = null - private var maxConnTotal: Int? = null - private var maxConnPerRoute: Int? = null - - /** - * Sets the API key. - * @param key The API key to use. - */ - fun key(key: String) = apply { + private var interceptors: List? = null + private var networkInterceptors: List? = null + private var connectionPool: ConnectionPool? = null + private var retryOnConnectionFailure: Boolean? = null + private var callTimeout: Int? = null + private var connectTimeout: Int? = null + private var readTimeout: Int? = null + private var writeTimeout: Int? = null + + fun key(key: String?): Builder { this.key = key + return this } - /** - * Sets the API secret. - * @param secret The API secret to use. - */ - fun secret(secret: String) = apply { + fun secret(secret: String?): Builder { this.secret = secret + return this } - /** - * Sets the environment (e.g., production, test, or sandbox). - * @param environment The `ClientEnvironment` to use. - */ fun environment(environment: ClientEnvironment) = apply { this.environment = environment } - /** - * Sets the request timeout in milliseconds. - * @param requestTimeout The request timeout duration. - */ - fun requestTimeout(requestTimeout: Long) = apply { - this.requestTimeout = requestTimeout + fun interceptors(interceptors: List) = apply { + this.interceptors = interceptors } - /** - * Sets the connection timeout in milliseconds. - * @param connectionTimeout The connection timeout duration. - */ - fun connectionTimeout(connectionTimeout: Long) = apply { - this.connectionTimeout = connectionTimeout + fun networkInterceptors(networkInterceptors: List) = apply { + this.networkInterceptors = networkInterceptors } - /** - * Sets the socket timeout in milliseconds. - * @param socketTimeout The socket timeout duration. - */ - fun socketTimeout(socketTimeout: Long) = apply { - this.socketTimeout = socketTimeout + fun connectionPool(connectionPool: ConnectionPool) = apply { + this.connectionPool = connectionPool } - /** - * Sets the headers whose values should be masked in logs. - * @param maskedLoggingHeaders A set of HTTP headers to mask in logs. - */ - fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { - this.maskedLoggingHeaders = maskedLoggingHeaders + fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean) = apply { + this.retryOnConnectionFailure = retryOnConnectionFailure } - /** - * Sets the body fields whose values should be masked in logs. - * @param maskedLoggingBodyFields A set of fields in the request body to mask in logs. - */ - fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { - this.maskedLoggingBodyFields = maskedLoggingBodyFields + fun callTimeout(callTimeout: Int) = apply { + this.callTimeout = callTimeout } - fun maxConnTotal(maxConnTotal: Int) = apply { - this.maxConnTotal = maxConnTotal + fun connectTimeout(connectTimeout: Int) = apply { + this.connectTimeout = connectTimeout } - fun maxConnPerRoute(maxConnPerRoute: Int) = apply { - this.maxConnPerRoute = maxConnPerRoute + fun readTimeout(readTimeout: Int) = apply { + this.readTimeout = readTimeout } - /** - * Builds and returns the `ClientConfiguration` instance. - * @return The configured `ClientConfiguration`. - */ - fun build(): ClientConfiguration { - return ClientConfiguration( - key, - secret, - environment, - requestTimeout, - connectionTimeout, - socketTimeout, - maskedLoggingHeaders, - maskedLoggingBodyFields, - maxConnTotal, - maxConnPerRoute, - ) + fun writeTimeout(writeTimeout: Int) = apply { + this.writeTimeout = writeTimeout } - } - companion object { - @JvmStatic - fun builder(): Builder = Builder() + fun build(): DefaultClientConfiguration { + require(key != null) { + "key is required" + } + + require(secret != null) { + "secret is required" + } + + return DefaultClientConfiguration( + key = key!!, + secret = secret!!, + environment = environment, + interceptors = interceptors, + networkInterceptors = networkInterceptors, + connectionPool = connectionPool, + retryOnConnectionFailure = retryOnConnectionFailure, + callTimeout = callTimeout, + connectTimeout = connectTimeout, + readTimeout = readTimeout, + writeTimeout = writeTimeout + ) + } } +} - internal fun toFullClientConfiguration(apiEndpoint: ApiEndpoint): FullClientConfiguration { - return object : FullClientConfiguration { - override fun getKey(): String = - key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") - - override fun getSecret(): String = - secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") - - override fun getEndpoint(): String = apiEndpoint.endpoint - - override fun getAuthEndpoint(): String = apiEndpoint.authEndpoint +data class CustomClientConfiguration( + override val key: String, + override val secret: String, + override val environment: ClientEnvironment, + val transport: Transport +) : ClientConfiguration(key, secret, environment) { - override fun getMaskedLoggingHeaders(): Set = - maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() + class Builder(private var transport: Transport) { + private var key: String? = null + private var secret: String? = null + private var environment: ClientEnvironment? = null - override fun getMaskedLoggingBodyFields(): Set = - maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() + fun key(key: String) = apply { + this.key = key + } - override fun getRequestTimeout(): Long = - requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() + fun secret(secret: String) = apply { + this.secret = secret + } - override fun getSocketTimeout(): Long = - socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() - override fun getConnectionTimeout(): Long = - connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() + fun environment(environment: ClientEnvironment) = apply { + this.environment = environment + } - override fun getAuthenticationStrategy(): AuthenticationStrategy = - ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() + fun build(): CustomClientConfiguration { + require(key != null) { + "key is required" + } - override fun getMaxConnectionsTotal(): Int = - maxConnTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() + require(secret != null) { + "secret is required" + } - override fun getMaxConnectionsPerRoute(): Int = - maxConnPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() + return CustomClientConfiguration( + key = key!!, + secret = secret!!, + environment = environment!!, + transport = transport + ) } } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt deleted file mode 100644 index e3f72c15..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/client/FileManagementClient.kt +++ /dev/null @@ -1,139 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.client - -import com.expediagroup.sdk.core.client.SdkClient -import com.expediagroup.sdk.core.configuration.DefaultClientBuilder -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment -import com.expediagroup.sdk.lodgingconnectivity.configuration.EndpointProvider -import com.expediagroup.sdk.lodgingconnectivity.configuration.FileManagementApiEndpointProvider -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.FileUploadRequest -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.Upload201Response -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileDownloadOperation -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileDownloadOperationParams -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileUploadOperation -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations.FileUploadOperationParams -import java.io.IOException -import java.io.OutputStream -import java.io.InputStream -import java.io.File - -class FileManagementClient( - configuration: ClientConfiguration -) { - private val client = SdkClient( - configuration = configuration.toFullClientConfiguration( - FileManagementApiEndpointProvider.forEnvironment( - environment = configuration.environment ?: ClientEnvironment.PROD - ) - ) - ) - - @Throws(IOException::class) - private fun buildOperation( - id: String, - type: String? = null, - value: String? = null - ) = FileDownloadOperation( - params = FileDownloadOperationParams( - id = id, - type = type, - value = value - ) - ) - - - @Throws(IOException::class) - @JvmOverloads - fun download( - id: String, - downloadTo: OutputStream, - type: String? = null, - value: String? = null - ) = - buildOperation( - id = id, - type = type, - value = value - ).let { - client.executeAndDownloadTo(it, downloadTo) - } - - @Throws(IOException::class) - @JvmOverloads - fun download( - id: String, - type: String? = null, - value: String? = null - ): InputStream? { - buildOperation( - id = id, - type = type, - value = value - ).let { - return client.executeAsInputStream(it) - } - } - - @Throws(IOException::class) - @JvmOverloads - fun upload( - file: File, - type: String? = null, - value: String? = null, - ): Upload201Response { - val params = FileUploadOperationParams( - type = type, - value = value - ) - - val body = FileUploadRequest(file) - - val operation = FileUploadOperation( - requestBody = body, - params = params, - ) - - return client.execute(operation, false) as Upload201Response - } - - @Throws(IOException::class) - @JvmOverloads - fun upload( - stream: InputStream, - type: String? = null, - value: String? = null, - fileContentType: String? = null, - fileExtension: String? = null - ): Upload201Response { - val params = FileUploadOperationParams( - type = type, - value = value - ) - - val body = FileUploadRequest(stream) - - val operation = FileUploadOperation( - requestBody = body, - params = params, - fileContentType = fileContentType, - fileExtension = fileExtension - ) - - return client.execute( - operation = operation, - enableGzipContent = false, - ) as Upload201Response - } - - companion object { - @JvmStatic - fun builder(): Builder = Builder() - - class Builder : DefaultClientBuilder() { - private val configurationBuilder = ClientConfiguration.builder() - - override fun build(): FileManagementClient = - FileManagementClient(configurationBuilder.build()) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt deleted file mode 100644 index 63d9532c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/FileUploadRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models - -import java.io.File -import java.io.InputStream - -class FileUploadRequest private constructor(val content: Any?) { - constructor(file: File) : this(content = file) - constructor(inputStream: InputStream) : this(content = inputStream) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt deleted file mode 100644 index 7c2192b0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/GenericError.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models - - -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import com.fasterxml.jackson.annotation.JsonProperty -import javax.validation.Valid - -/** - * The object used the describe an error, containing both human-readable and in a machine-readable information. - * @param type Snake cased all caps error code interpreted from the HTTP status code that can programmatically be acted upon. - * @param detail A human-readable explanation of the error, specific to this error occurrence. - */ -data class GenericError( - /* Snake cased all caps error code interpreted from the HTTP status code that can programmatically be acted upon. */ - @JsonProperty("type") - - - @field:Valid - val type: kotlin.String, - - /* A human-readable explanation of the error, specific to this error occurrence. */ - @JsonProperty("detail") - - - @field:Valid - val detail: kotlin.String -) { - - - companion object { - @JvmStatic - fun builder() = Builder() - } - - class Builder( - private var type: kotlin.String? = null, - private var detail: kotlin.String? = null - ) { - fun type(type: kotlin.String) = apply { this.type = type } - fun detail(detail: kotlin.String) = apply { this.detail = detail } - - fun build(): GenericError { - // Check required params - validateNullity() - return GenericError( - type = type!!, - detail = detail!! - ) - } - - private fun validateNullity() { - if (type == null) { - throw NullPointerException("Required parameter type is missing") - } - if (detail == null) { - throw NullPointerException("Required parameter detail is missing") - } - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt deleted file mode 100644 index 27cd4871..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/Upload201Response.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models - - -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import com.fasterxml.jackson.annotation.JsonProperty -import javax.validation.Valid - -/** - * - * @param id File identifier - */ -data class Upload201Response( - /* File identifier */ - @JsonProperty("id") - - - @field:Valid - val id: kotlin.String? = null -) { - - - companion object { - @JvmStatic - fun builder() = Builder() - } - - class Builder( - private var id: kotlin.String? = null - ) { - fun id(id: kotlin.String?) = apply { this.id = id } - - fun build(): Upload201Response { - return Upload201Response( - id = id - ) - } - - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt deleted file mode 100644 index fe74cbb7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolation.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception - -/** - * An entity to represent a constraint violation of a property. - * - * @property name The name of the constraint-violated field - * @property path The path of the constraint-violated field - * @property message The constraint violation message - */ -data class PropertyConstraintViolation( - val name: String, - val path: String, - val message: String -) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt deleted file mode 100644 index 7500d625..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/models/exception/PropertyConstraintViolationException.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception - -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupClientException - -/** - * An exception to be thrown when a constraint on some property has been violated. - * - * @property message The detail message. - * @property constraintViolations A list of the constraint violations that occurred - */ -class PropertyConstraintViolationException( - message: String, - val constraintViolations: List -) : ExpediaGroupClientException("$message ${constraintViolations.joinToString(separator = ",\n\t- ", prefix = "[\n\t- ", postfix = "\n]")}") diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt deleted file mode 100644 index 2aecc192..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileDownloadOperation.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations - -import com.expediagroup.sdk.core.model.Nothing -import com.expediagroup.sdk.core.model.Operation -import com.expediagroup.sdk.core.model.OperationParams - -class FileDownloadOperation( - params: FileDownloadOperationParams, -) : Operation( - url = "/supply-lodging/v1/files/${params.id}/content", - method = "GET", - operationId = "downloadFile", - requestBody = null, - params = params, -) - -class FileDownloadOperationParams( - val id: String, - val type: String?, - val value: String?, -) : OperationParams { - override fun getHeaders(): Map = emptyMap() - - override fun getQueryParams(): Map> = buildMap { - if (!type.isNullOrBlank()) put("type", listOf(type)) - if (!value.isNullOrBlank()) put("value", listOf(value)) - } - - override fun getPathParams(): Map = buildMap { - put("id", id) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt deleted file mode 100644 index 83bc3d31..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/operations/FileUploadOperation.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.operations - -import com.expediagroup.sdk.core.http.BlobTypeDetector -import com.expediagroup.sdk.core.model.OperationParams -import com.expediagroup.sdk.core.model.Operation -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.FileUploadRequest -import com.google.api.client.http.FileContent -import com.google.api.client.http.HttpContent -import com.google.api.client.http.HttpHeaders -import com.google.api.client.http.HttpMediaType -import com.google.api.client.http.InputStreamContent -import com.google.api.client.http.MultipartContent -import com.google.api.client.http.MultipartContent.Part -import org.apache.tika.mime.MimeTypes -import java.io.File -import java.io.InputStream -import java.util.UUID - - -class FileUploadOperation( - requestBody: FileUploadRequest, - params: FileUploadOperationParams, - private var fileContentType: String? = null, - private var fileExtension: String? = null, -) : Operation( - url = "/supply-lodging/v1/files", - method = "POST", - operationId = "uploadFile", - requestBody = requestBody, - params = params, - isUpload = true, -) { - init { - if (fileContentType == null) { - when (requestBody.content) { - is File -> fileContentType = BlobTypeDetector.getInstance().detect(requestBody.content) - is InputStream -> fileContentType = BlobTypeDetector.getInstance().detect(requestBody.content) - else -> throw IllegalArgumentException("Unsupported content type") - } - } - - if (fileExtension == null) { - when (requestBody.content) { - is File -> fileExtension = requestBody.content.extension - is InputStream -> fileExtension = MimeTypes.getDefaultMimeTypes().forName(fileContentType).extension - else -> throw IllegalArgumentException("Unsupported content type") - } - } - } - - override fun getHttpContent(): HttpContent { - return MultipartContent().apply { - // Set the overall media type for the multipart content - setMediaType(HttpMediaType("multipart/form-data").apply { - setBoundary("__END_OF_PART__${UUID.randomUUID()}") - }) - - val fileName = when (requestBody?.content) { - is File -> requestBody.content.name - is InputStream -> "file.$fileExtension" - else -> throw IllegalArgumentException("Unsupported content type") - } - - // Add the file part - addPart(Part().apply { - headers = HttpHeaders().apply { - setContentType("application/octet-stream") - set("Content-Disposition", "form-data; name=\"content\"; filename=\"$fileName\"") - } - - content = when (requestBody.content) { - is File -> FileContent(fileContentType, requestBody.content.absoluteFile) - is InputStream -> InputStreamContent(fileContentType, requestBody.content) - else -> throw IllegalArgumentException("Unsupported content type") - }.apply { - setBoundary("__END_OF_PART__${UUID.randomUUID()}") - } - }) - } - } -} - -class FileUploadOperationParams( - val type: String? = null, - val value: String? = null, -) : OperationParams { - override fun getHeaders(): Map = emptyMap() - - override fun getQueryParams(): Map> = - buildMap { - if (!type.isNullOrBlank()) put("type", listOf(type)) - if (!value.isNullOrBlank()) put("value", listOf(value)) - } - - override fun getPathParams(): Map = - emptyMap() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt deleted file mode 100644 index 86fd64dd..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/filemanagement/validation/PropertyConstraintsValidator.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -package com.expediagroup.sdk.lodgingconnectivity.filemanagement.validation - -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolation -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.models.exception.PropertyConstraintViolationException -import javax.validation.ConstraintViolation -import javax.validation.Validation -import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator -import java.util.stream.Collectors - -internal object PropertyConstraintsValidator { - fun validateConstraints(obj: Any?) { - obj?.let { - Validation.byDefaultProvider() - .configure() - .messageInterpolator(ParameterMessageInterpolator()) - .buildValidatorFactory().use { factory -> - val violations = factory.validator.validate(obj) - if (violations.isNotEmpty()) { - throw PropertyConstraintViolationException( - "Some field constraints have been violated", - violations.stream().map { toConstraintViolation(it) }.collect(Collectors.toList()) - ) - } - } - } - } - - private fun toConstraintViolation(violation: ConstraintViolation<*>): PropertyConstraintViolation { - return PropertyConstraintViolation( - violation.propertyPath.iterator().next().name, - violation.propertyPath.toString(), - violation.message - ) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt index 906c1b19..df35a674 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/payment/PaymentClient.kt @@ -19,6 +19,7 @@ package com.expediagroup.sdk.lodgingconnectivity.payment import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor import com.expediagroup.sdk.graphql.common.GraphQLClient import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.PaymentApiEndpointProvider @@ -35,12 +36,11 @@ import com.expediagroup.sdk.lodgingconnectivity.payment.operation.getPaymentInst * or timeouts. */ class PaymentClient(config: ClientConfiguration) : GraphQLClient() { + override val apiEndpoint = PaymentApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.PROD) + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( - config.toFullClientConfiguration( - apiEndpoint = PaymentApiEndpointProvider.forEnvironment( - environment = config.environment ?: ClientEnvironment.PROD - ), - ) + requestExecutor = DefaultRequestExecutor(config, apiEndpoint), + serverUrl = apiEndpoint.endpoint ) /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt index fdbdb49d..b3eb89a2 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/sandbox/SandboxDataManagementClient.kt @@ -19,6 +19,7 @@ package com.expediagroup.sdk.lodgingconnectivity.sandbox import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor import com.expediagroup.sdk.graphql.common.GraphQLClient import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.SandboxApiEndpointProvider @@ -73,12 +74,11 @@ import com.expediagroup.sdk.lodgingconnectivity.sandbox.reservation.paginator.Sa * or timeouts. */ class SandboxDataManagementClient(config: ClientConfiguration) : GraphQLClient() { + override val apiEndpoint = SandboxApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.SANDBOX_PROD) + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( - config.toFullClientConfiguration( - apiEndpoint = SandboxApiEndpointProvider.forEnvironment( - environment = config.environment ?: ClientEnvironment.SANDBOX_PROD - ), - ) + requestExecutor = DefaultRequestExecutor(config, apiEndpoint), + serverUrl = apiEndpoint.endpoint ) /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt index b44d8cf8..7ce34c30 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/supply/reservation/ReservationClient.kt @@ -19,6 +19,7 @@ package com.expediagroup.sdk.lodgingconnectivity.supply.reservation import com.expediagroup.sdk.graphql.common.DefaultGraphQLExecutor import com.expediagroup.sdk.graphql.common.GraphQLClient import com.expediagroup.sdk.graphql.common.GraphQLExecutor +import com.expediagroup.sdk.lodgingconnectivity.common.DefaultRequestExecutor import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment import com.expediagroup.sdk.lodgingconnectivity.configuration.SupplyApiEndpointProvider @@ -58,12 +59,12 @@ import com.expediagroup.sdk.lodgingconnectivity.supply.reservation.stream.Reserv * timeouts */ class ReservationClient(config: ClientConfiguration) : GraphQLClient() { + + override val apiEndpoint = SupplyApiEndpointProvider.forEnvironment(config.environment ?: ClientEnvironment.PROD) + override val graphQLExecutor: GraphQLExecutor = DefaultGraphQLExecutor( - config.toFullClientConfiguration( - apiEndpoint = SupplyApiEndpointProvider.forEnvironment( - environment = config.environment ?: ClientEnvironment.PROD - ), - ) + requestExecutor = DefaultRequestExecutor(config, apiEndpoint), + serverUrl = apiEndpoint.endpoint ) /** diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java deleted file mode 100644 index e0119978..00000000 --- a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/FileManagementExample.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity; - -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration; -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientEnvironment; -import com.expediagroup.sdk.lodgingconnectivity.filemanagement.client.FileManagementClient; - -import java.io.File; -import java.io.IOException; - -public class FileManagementExample { - static FileManagementClient client = new FileManagementClient( - ClientConfiguration.builder() - .key("KEY") - .secret("SECRET") - .environment(ClientEnvironment.TEST) - .build() - ); - - public static void main(String[] args) throws IOException { - client.upload( - new File("code/src/main/resources/test.txt"), - "messageThreadId", - "6ddb1317-bf3d-4759-bcdf-d52642cfac88" - ); - } -} diff --git a/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java b/examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java similarity index 100% rename from examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/LodgingSupplySandboxDataManagementClientUsageExample.java rename to examples/src/main/java/com/expediagroup/sdk/lodgingconnectivity/SandboxDataManagementClientUsageExample.java From cedb0a9db94da5e99fe656bc684eedb86d77c482 Mon Sep 17 00:00:00 2001 From: Mohammad Dwairi <49045447+Mohammad-Dwairi@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:45:31 +0300 Subject: [PATCH 07/15] feat: cleanup old core package (#110) --- code/build.gradle | 16 +- .../apache/util/ApacheHttpTransportUtil.kt | 93 ------- .../bearer/BearerAuthenticationInterceptor.kt | 13 +- .../bearer/BearerAuthenticationManager.kt | 20 +- .../bearer/BearerTokenStorage.kt | 2 +- .../authentication/bearer/TokenResponse.kt | 2 +- .../common/AuthenticationManager.kt | 2 +- .../authentication/common/Credentials.kt | 2 +- ...2CredentialsWithRefreshBuilderExtension.kt | 17 -- .../strategy/AuthenticationStrategy.kt | 13 - .../BearerAuthenticationHandlerFactory.kt | 149 ----------- .../util/HttpCredentialsAdapterUtil.kt | 87 ------- .../expediagroup/sdk/core/client/ApiClient.kt | 43 --- .../core/client/ApiClientApolloHttpEngine.kt | 84 ------ .../sdk/core/client/ApiClientBuilder.kt | 48 ---- .../{core2 => core}/client/RequestExecutor.kt | 10 +- .../expediagroup/sdk/core/client/SdkClient.kt | 102 -------- .../sdk/{core2 => core}/client/Transport.kt | 6 +- .../sdk/core/client/util/ApiClientUtil.kt | 39 --- .../sdk/core/client/util/GsonFactoryUtil.kt | 18 -- .../configuration/DefaultClientBuilder.kt | 75 ------ .../ExpediaGroupDefaultClientConfiguration.kt | 45 ---- .../configuration/FullClientConfiguration.kt | 148 ----------- .../sdk/core/configuration/SdkMetadata.kt | 23 -- .../sdk/core/constant/Authentication.kt | 5 - .../sdk/core/constant/Constant.kt | 32 --- .../sdk/core/constant/ExceptionMessage.kt | 24 -- .../sdk/core/constant/HeaderKey.kt | 26 -- .../sdk/core/constant/HeaderValue.kt | 20 -- .../com/expediagroup/sdk/core/constant/Key.kt | 26 -- .../sdk/core/constant/LogMaskingFields.kt | 38 --- .../sdk/core/constant/LoggerName.kt | 21 -- .../sdk/core/constant/LoggingMessage.kt | 28 -- .../provider/ExceptionMessageProvider.kt | 29 --- .../provider/LoggingMessageProvider.kt | 50 ---- .../sdk/core/extension/NullableExtension.kt | 6 +- .../sdk/core/http/BlobTypeDetector.kt | 21 -- .../sdk/{core2 => core}/http/ContentType.kt | 4 +- .../sdk/{core2 => core}/http/Headers.kt | 4 +- .../expediagroup/sdk/core/http/HttpStatus.kt | 93 ------- .../sdk/{core2 => core}/http/MediaType.kt | 2 +- .../sdk/{core2 => core}/http/Protocol.kt | 2 +- .../sdk/{core2 => core}/http/Request.kt | 2 +- .../sdk/{core2 => core}/http/RequestBody.kt | 2 +- .../sdk/{core2 => core}/http/Response.kt | 2 +- .../sdk/{core2 => core}/http/ResponseBody.kt | 2 +- .../sdk/{core2 => core}/http/Status.kt | 3 +- .../sdk/{core2 => core}/http/Url.kt | 4 +- .../interceptor/Interceptor.kt | 6 +- .../interceptor/InterceptorsChainExecutor.kt | 8 +- .../expediagroup/sdk/core/jackson/Config.kt | 7 - .../com/expediagroup/sdk/core/jackson/Util.kt | 25 -- .../sdk/core/logging/ExpediaGroupLogger.kt | 72 ----- .../core/logging/ExpediaGroupLoggerFactory.kt | 30 --- .../sdk/core/logging/LogMessageConstants.kt | 23 -- .../logging/LoggingInterceptor.kt | 12 +- .../logging/common/Constant.kt | 2 +- .../logging/{ => common}/LogMessageTag.kt | 2 +- .../{ => common}/LoggableContentTypes.kt | 4 +- .../logging/common/LoggerDecorator.kt | 4 +- .../mask/ExpediaGroupJsonFieldFilter.kt | 19 -- .../ExpediaGroupJsonFieldPatternBuilder.kt | 17 -- .../sdk/core/logging/mask/MaskLogs.kt | 100 ------- .../logging/masking/JsonFieldFilter.kt | 2 +- .../masking/JsonFieldPatternBuilder.kt | 4 +- .../logging/masking/MaskLogsUtils.kt | 2 +- .../expediagroup/sdk/core/model/Nothing.kt | 21 -- .../expediagroup/sdk/core/model/Operation.kt | 33 --- .../sdk/core/model/OperationParams.kt | 22 -- .../expediagroup/sdk/core/model/Properties.kt | 36 --- .../expediagroup/sdk/core/model/Response.kt | 63 ----- .../sdk/core/model/TransactionId.kt | 30 --- .../expediagroup/sdk/core/model/UserAgent.kt | 21 -- .../ExpediaGroupInvalidFieldNameException.kt | 30 --- .../service/ExpediaGroupApiException.kt | 36 --- .../service/ExpediaGroupAuthException.kt | 6 +- .../okhttp/BaseOkHttpClient.kt | 2 +- .../okhttp/OkHttpClientConfiguration.kt | 2 +- .../{core2 => core}/okhttp/OkHttpTransport.kt | 22 +- .../expediagroup/sdk/core/request/Request.kt | 246 ------------------ .../ApiClientRequestInitializer.kt | 73 ------ .../initializer/InitializerFunctions.kt | 20 -- .../initializer/SdkRequestInitializer.kt | 28 -- .../HttpRequestLoggingInterceptor.kt | 125 --------- .../HttpResponseLoggingInterceptor.kt | 126 --------- .../com/expediagroup/sdk/core/trait/Trait.kt | 7 - .../AuthenticationHandlerTrait.kt | 19 -- .../authentication/RefreshAccessTokenTrait.kt | 15 -- .../sdk/core/trait/common/BuilderTrait.kt | 12 - .../core/trait/common/ConfigurationTrait.kt | 15 -- .../sdk/core/trait/common/IdTrait.kt | 16 -- .../trait/configuration/AuthEndpointTrait.kt | 12 - .../AuthenticationStrategyTrait.kt | 17 -- .../configuration/ClientConfiguration.kt | 16 -- .../configuration/ClientConfigurationTrait.kt | 10 - .../configuration/ConnectionTimeoutTrait.kt | 13 - .../core/trait/configuration/EndpointTrait.kt | 11 - .../configuration/FullConfigurationTrait.kt | 33 --- .../sdk/core/trait/configuration/KeyTrait.kt | 15 -- .../MaskedLoggingBodyFieldsTrait.kt | 14 - .../MaskedLoggingHeadersTrait.kt | 14 - .../MaxConnectionsPerRouteTrait.kt | 16 -- .../configuration/MaxConnectionsTotalTrait.kt | 15 -- .../configuration/RequestTimeoutTrait.kt | 15 -- .../core/trait/configuration/SecretTrait.kt | 16 -- .../trait/configuration/SocketTimeoutTrait.kt | 15 -- .../ToClientConfigurationTrait.kt | 13 - .../sdk/core2/extension/NullableExtension.kt | 13 - .../sdk/core2/logging/common/LogMessageTag.kt | 16 -- .../logging/common/LoggableContentTypes.kt | 21 -- .../sdk/graphql/common/ApolloAliases.kt | 2 + .../sdk/graphql/common/ApolloHttpEngine.kt | 10 +- .../graphql/common/DefaultGraphQLExecutor.kt | 2 +- .../common/DefaultRequestExecutor.kt | 27 +- .../common/RequestExecutorImpl.kt | 48 ++++ .../configuration/ClientConfiguration.kt | 4 +- docs/logging.md | 12 +- 117 files changed, 168 insertions(+), 3100 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/bearer/BearerAuthenticationInterceptor.kt (91%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/bearer/BearerAuthenticationManager.kt (87%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/bearer/BearerTokenStorage.kt (98%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/bearer/TokenResponse.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/common/AuthenticationManager.kt (97%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/authentication/common/Credentials.kt (97%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/client/RequestExecutor.kt (91%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/client/Transport.kt (94%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/ExceptionMessage.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderKey.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderValue.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/Key.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggerName.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/ContentType.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Headers.kt (98%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/http/HttpStatus.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/MediaType.kt (99%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Protocol.kt (97%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Request.kt (99%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/RequestBody.kt (99%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Response.kt (99%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/ResponseBody.kt (98%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Status.kt (96%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/http/Url.kt (99%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/interceptor/Interceptor.kt (95%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/interceptor/InterceptorsChainExecutor.kt (93%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Config.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/jackson/Util.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/LoggingInterceptor.kt (91%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/common/Constant.kt (94%) rename code/src/main/kotlin/com/expediagroup/sdk/core/logging/{ => common}/LogMessageTag.kt (88%) rename code/src/main/kotlin/com/expediagroup/sdk/core/logging/{ => common}/LoggableContentTypes.kt (88%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/common/LoggerDecorator.kt (96%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/masking/JsonFieldFilter.kt (92%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/masking/JsonFieldPatternBuilder.kt (85%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/logging/masking/MaskLogsUtils.kt (98%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/Nothing.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/Properties.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/okhttp/BaseOkHttpClient.kt (97%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/okhttp/OkHttpClientConfiguration.kt (98%) rename code/src/main/kotlin/com/expediagroup/sdk/{core2 => core}/okhttp/OkHttpTransport.kt (83%) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt diff --git a/code/build.gradle b/code/build.gradle index b25eb092..31572d75 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -34,20 +34,14 @@ dependencies { implementation 'com.apollographql.adapters:apollo-adapters-core:0.0.4' /* Apollo Java */ - implementation("com.apollographql.java:client:0.0.2") + implementation 'com.apollographql.java:client:0.0.2' /* EG SDK Core */ - implementation("org.apache.tika:tika-core:2.9.2") - implementation("com.google.api-client:google-api-client:2.7.0") - implementation("com.ebay.ejmask:ejmask-api:1.0.3") - implementation("com.ebay.ejmask:ejmask-extensions:1.0.3") - implementation 'org.slf4j:slf4j-simple:2.0.13' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' - implementation 'org.jetbrains.kotlinx:atomicfu-jvm:0.24.0' + implementation 'com.ebay.ejmask:ejmask-api:1.0.3' + implementation 'com.ebay.ejmask:ejmask-extensions:1.0.3' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.18.1' - implementation 'javax.validation:validation-api:2.0.1.Final' - implementation 'org.hibernate.validator:hibernate-validator:6.2.5.Final' - implementation 'jakarta.validation:jakarta.validation-api:2.0.2' + implementation 'org.slf4j:slf4j-api:2.0.16' } apply from: "tasks-gradle/apollo.gradle" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt deleted file mode 100644 index 31af39d9..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/apache/util/ApacheHttpTransportUtil.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.expediagroup.sdk.core.apache.util - -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsPerRouteTrait -import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsTotalTrait -import com.expediagroup.sdk.core.trait.configuration.SocketTimeoutTrait -import com.google.api.client.http.apache.v2.ApacheHttpTransport -import org.apache.http.client.HttpClient -import org.apache.http.client.config.RequestConfig -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - - -fun createApacheHttpTransport(configuration: ClientConfiguration): ApacheHttpTransport = - ApacheHttpTransport(createHttpClient(configuration)) - -fun getSingletonApacheHttpTransport(configuration: ClientConfiguration): ApacheHttpTransport = - ApacheHttpTransportSingleton.getTransport(configuration) - - -/** - * Singleton object for managing Apache HTTP transports. - * - * This singleton object provides a thread-safe way to manage a collection of `ApacheHttpTransport` - * instances identified by a `ClientConfiguration`'s UUID. The transport instances are stored - * in a `ConcurrentMap` to prevent duplicate creation and ensure efficient reuse. - * - * Functions: - * - getTransport(configuration: ClientConfiguration): Retrieves an existing `ApacheHttpTransport` - * instance if available, or creates a new one based on the given `ClientConfiguration`. - */ -private object ApacheHttpTransportSingleton { - private val transports: ConcurrentMap = ConcurrentHashMap() - - - /** - * Retrieves or creates an `ApacheHttpTransport` instance based on the provided `ClientConfiguration`. - * - * If an `ApacheHttpTransport` associated with the given configuration ID already exists, it is returned. - * Otherwise, a new `ApacheHttpTransport` is created using the provided configuration. - * - * @param configuration The `ClientConfiguration` instance containing the necessary traits and configurations - * for initializing or retrieving the `ApacheHttpTransport`. - * @return An `ApacheHttpTransport` instance associated with the given `ClientConfiguration`. - */ - fun getTransport(configuration: ClientConfiguration): ApacheHttpTransport = - transports.getOrPut(configuration.id) { - createApacheHttpTransport(configuration) - } -} - -/** - * Creates an `HttpClient` instance configured according to the specified `ClientConfiguration`. - * - * The method ensures that the provided `configuration` implements the necessary traits - * (`MaxConnectionsTotalTrait` and `MaxConnectionsPerRouteTrait`) required for setting up - * the `HttpClient`. - * - * @param configuration The `ClientConfiguration` instance containing the necessary traits - * and configurations for initializing the `HttpClient`. - * @return An `HttpClient` instance configured based on the provided `configuration`. - */ -fun createHttpClient(configuration: ClientConfiguration): HttpClient { - require(configuration is MaxConnectionsTotalTrait) { "Configuration must implement MaxConnectionsTotalTrait" } - require(configuration is MaxConnectionsPerRouteTrait) { "Configuration must implement MaxConnectionsPerRouteTrait" } - - return ApacheHttpTransport.newDefaultHttpClientBuilder() - .setDefaultRequestConfig(createRequestConfig(configuration)) - .setMaxConnTotal((configuration as MaxConnectionsTotalTrait).getMaxConnectionsTotal()) - .setMaxConnPerRoute((configuration as MaxConnectionsPerRouteTrait).getMaxConnectionsPerRoute()) - .build() -} - -/** - * Creates a `RequestConfig` instance based on the specified `ClientConfiguration`. - * - * This method ensures that the provided `configuration` implements the necessary - * `SocketTimeoutTrait` required for configuring the socket timeout settings in - * the `RequestConfig`. - * - * @param configuration The `ClientConfiguration` instance providing the socket timeout settings. - * The configuration must implement `SocketTimeoutTrait`. - * @return A `RequestConfig` instance configured with the socket timeout properties from the specified configuration. - */ -internal fun createRequestConfig(configuration: ClientConfiguration): RequestConfig { - require(configuration is SocketTimeoutTrait) { "Configuration must implement SocketTimeoutTrait" } - - return RequestConfig.copy(RequestConfig.DEFAULT) - .setConnectTimeout(configuration.getSocketTimeout().toInt()) - .setSocketTimeout(configuration.getSocketTimeout().toInt()) - .build() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt similarity index 91% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt index 81413365..fc7c8263 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt @@ -14,16 +14,15 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.bearer +package com.expediagroup.sdk.core.authentication.bearer +import com.expediagroup.sdk.core.authentication.common.Credentials +import com.expediagroup.sdk.core.client.Transport +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.expediagroup.sdk.core2.authentication.common.Credentials -import com.expediagroup.sdk.core2.client.Transport -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response -import com.expediagroup.sdk.core2.interceptor.Interceptor import java.io.IOException -import kotlin.jvm.Throws /** * An interceptor that handles bearer token-based authentication for HTTP requests. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt similarity index 87% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index f2ae99d4..771e4344 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -14,19 +14,19 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.bearer +package com.expediagroup.sdk.core.authentication.bearer +import com.expediagroup.sdk.core.authentication.common.AuthenticationManager +import com.expediagroup.sdk.core.authentication.common.Credentials +import com.expediagroup.sdk.core.client.Transport import com.expediagroup.sdk.core.extension.getOrThrow -import com.expediagroup.sdk.core.http.HttpStatus +import com.expediagroup.sdk.core.http.ContentType +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.RequestBody +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.http.Status import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.expediagroup.sdk.core2.authentication.common.AuthenticationManager -import com.expediagroup.sdk.core2.authentication.common.Credentials -import com.expediagroup.sdk.core2.client.Transport -import com.expediagroup.sdk.core2.http.ContentType -import com.expediagroup.sdk.core2.http.RequestBody -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.registerKotlinModule @@ -95,7 +95,7 @@ class BearerAuthenticationManager( private fun parseAuthenticationResponse(response: Response): TokenResponse { val responseBody = response.body.getOrThrow { - ExpediaGroupAuthException(HttpStatus.INTERNAL_SERVER_ERROR, "Authentication response body is empty") + ExpediaGroupAuthException(Status.INTERNAL_SERVER_ERROR, "Authentication response body is empty") } val responseString = responseBody.source().use { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt index 416d5f17..b16717aa 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/BearerTokenStorage.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.bearer +package com.expediagroup.sdk.core.authentication.bearer import java.time.Clock import java.time.Instant diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt index a3dd271f..cbf71901 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/bearer/TokenResponse.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.bearer +package com.expediagroup.sdk.core.authentication.bearer import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt similarity index 97% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt index 33746755..13a477f5 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/AuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.common +package com.expediagroup.sdk.core.authentication.common import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/Credentials.kt similarity index 97% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/Credentials.kt index 2d865c49..dc690ce0 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/authentication/common/Credentials.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/Credentials.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.authentication.common +package com.expediagroup.sdk.core.authentication.common import java.nio.charset.Charset import java.nio.charset.StandardCharsets.ISO_8859_1 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt deleted file mode 100644 index b10c2566..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/extension/OAuth2CredentialsWithRefreshBuilderExtension.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.expediagroup.sdk.core.authentication.extension - -import com.expediagroup.sdk.core.constant.Authentication -import com.google.auth.oauth2.OAuth2CredentialsWithRefresh -import java.time.Duration - -/** - * Configures the `OAuth2CredentialsWithRefresh.Builder` with default configuration settings. - * - * This method sets the `expirationMargin` and `refreshMargin` to default values defined by `Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS`. - * - * @return The `OAuth2CredentialsWithRefresh.Builder` instance with default configurations applied. - */ -fun OAuth2CredentialsWithRefresh.Builder.withDefaultConfigurations(): OAuth2CredentialsWithRefresh.Builder = apply { - expirationMargin = Duration.ofSeconds(Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS) - refreshMargin = Duration.ofSeconds(Authentication.BEARER_EXPIRY_MARGIN_IN_SECONDS) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt deleted file mode 100644 index d7ba9fc7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/AuthenticationStrategy.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.expediagroup.sdk.core.authentication.strategy - -import com.expediagroup.sdk.core.trait.authentication.AuthenticationHandlerTrait - -/** - * Enumeration that represents different authentication strategies for a client configuration. - * - * @property handlerFactory A factory that creates an instance of the authentication handler - * trait for the specific strategy. - */ -enum class AuthenticationStrategy(val handlerFactory: AuthenticationHandlerTrait) { - BEARER(BearerAuthenticationHandlerFactory), -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt deleted file mode 100644 index fd6fe13b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/strategy/BearerAuthenticationHandlerFactory.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.expediagroup.sdk.core.authentication.strategy - -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.expediagroup.sdk.core.constant.LoggingMessage -import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.core.logging.LogMessageTag -import com.expediagroup.sdk.core.request.initializer.SdkRequestInitializer -import com.expediagroup.sdk.core.trait.authentication.AuthenticationHandlerTrait -import com.expediagroup.sdk.core.trait.authentication.RefreshAccessTokenTrait -import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.core.trait.configuration.KeyTrait -import com.expediagroup.sdk.core.trait.configuration.SecretTrait -import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest -import com.google.api.client.auth.oauth2.TokenRequest -import com.google.api.client.auth.oauth2.TokenResponse -import com.google.api.client.http.BasicAuthentication -import com.google.api.client.http.GenericUrl -import com.google.api.client.http.HttpRequestInitializer -import com.google.api.client.http.HttpTransport -import com.google.api.client.json.gson.GsonFactory -import com.google.auth.oauth2.AccessToken -import java.time.Instant -import java.util.Date - -/** - * Factory object for creating Bearer Authentication handlers. - */ -object BearerAuthenticationHandlerFactory : AuthenticationHandlerTrait { - - /** - * Creates an authentication handler to refresh access tokens using client credentials. - * - * @param config The client configuration that must implement KeyTrait, SecretTrait, and AuthEndpointTrait. - * @param transport The HTTP transport to be used for token refresh requests. - * @return An instance of RefreshAccessTokenTrait which can be used to refresh access tokens. - */ - override fun createAuthenticationHandler( - config: ClientConfiguration, - transport: HttpTransport, - ): RefreshAccessTokenTrait { - - require(config is KeyTrait) { "Configuration must implement KeyTrait!" } - require(config is SecretTrait) { "Configuration must implement SecretTrait!" } - require(config is AuthEndpointTrait) { "Configuration must implement AuthEndpointTrait!" } - - return object : RefreshAccessTokenTrait { - private val logger = ExpediaGroupLoggerFactory.getLogger(javaClass) - - /** - * Refreshes and returns a new `AccessToken` by making a token request. - * This method builds the token request, initializes it, logs the token renewal progress, - * executes the request, and handles any exceptions that occur. - * - * @return A newly generated `AccessToken` instance if the token request is successful. - * @throws ExpediaGroupAuthException if the token renewal process fails. - */ - override fun refreshAccessToken(): AccessToken = - buildTokenRequest() - .also attachDefaultInitializer@{ request -> - request.requestInitializer = attachSdkRequestInitializer(request) - }.also logTokenRenewalInProgress@{ - logger.info(LoggingMessage.TOKEN_RENEWAL_IN_PROGRESS, LogMessageTag.PROGRESSING) - }.let executeRequest@{ request -> - try { - return@executeRequest buildAccessToken(request.execute()) - } catch (e: Exception) { - logger.error(LoggingMessage.TOKEN_RENEWAL_FAILURE, LogMessageTag.ERROR) - throw ExpediaGroupAuthException( - message = "Token renewal failed!", - cause = e - ) - } - } - - - /** - * Builds a `ClientCredentialsTokenRequest` using the configuration traits available in the `config` property. - * Key, secret, and authentication endpoint are retrieved from the configuration. - * - * @return An instance of `ClientCredentialsTokenRequest` configured with client authentication and other necessary parameters. - */ - private fun buildTokenRequest(): ClientCredentialsTokenRequest { - val key = (config as KeyTrait).getKey() - val secret = (config as SecretTrait).getSecret() - val authEndpoint = (config as AuthEndpointTrait).getAuthEndpoint() - - return ClientCredentialsTokenRequest( - transport, - GsonFactory.getDefaultInstance(), - GenericUrl(authEndpoint), - ).setClientAuthentication( - BasicAuthentication(key, secret) - ) - } - - /** - * Calculates the token expiration time based on the current time and the duration specified in the token response. - * - * @param response The `TokenResponse` object containing the `expiresInSeconds` property, which indicates the time in seconds - * for which the token is valid from the current time. - * @return A `Date` object representing the calculated expiration time of the token. - */ - private fun calculateTokenExpirationTime(response: TokenResponse): Date = - Date.from(Instant.now().plusSeconds(response.expiresInSeconds.toLong())) - - /** - * Builds an `AccessToken` object from the given `TokenResponse`. - * Logs a success message upon successful token renewal. - * - * @param response The `TokenResponse` from which the access token is created. - * @return An instance of `AccessToken` containing the token value, expiration time, and scopes. - */ - private fun buildAccessToken(response: TokenResponse): AccessToken = - AccessToken.newBuilder() - .setTokenValue(response.accessToken) - .setExpirationTime(calculateTokenExpirationTime(response)) - .setScopes(response.scope) - .build().also { - logger.info( - LoggingMessage.TOKEN_RENEWAL_SUCCESSFUL, - LogMessageTag.SUCCESS - ) - } - - /** - * Attaches an appropriate SDK request initializer to the given `TokenRequest` instance based on the type of - * `requestInitializer` present in the `TokenRequest`. - * - * @param request The `TokenRequest` object that requires an initializer. The function checks the type of - * `requestInitializer` within this request and attaches an appropriate `SdkRequestInitializer`. - * If no suitable initializer is found, a default `SdkRequestInitializer` is used. - */ - private fun attachSdkRequestInitializer(request: TokenRequest) = - when (request.requestInitializer) { - is SdkRequestInitializer -> - (request.requestInitializer as SdkRequestInitializer).add( - SdkRequestInitializer.default() - ) - - is HttpRequestInitializer -> - SdkRequestInitializer.default().add(request.requestInitializer) - - else -> SdkRequestInitializer.default() - } - } - - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt deleted file mode 100644 index 0134b410..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/util/HttpCredentialsAdapterUtil.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.expediagroup.sdk.core.authentication.util - -import com.expediagroup.sdk.core.apache.util.createApacheHttpTransport -import com.expediagroup.sdk.core.authentication.extension.withDefaultConfigurations -import com.expediagroup.sdk.core.trait.common.IdTrait -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.core.trait.configuration.KeyTrait -import com.expediagroup.sdk.core.trait.configuration.SecretTrait -import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait -import com.expediagroup.sdk.core.trait.configuration.AuthenticationStrategyTrait -import com.google.auth.http.HttpCredentialsAdapter -import com.google.auth.oauth2.AccessToken -import com.google.auth.oauth2.OAuth2CredentialsWithRefresh -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - -/** - * Creates and returns an HttpCredentialsAdapter based on the given ClientConfiguration. - * The configuration must implement KeyTrait, SecretTrait, AuthEndpointTrait, and AuthenticationStrategyTrait. - * - * @param configuration The configuration object that must implement the necessary traits for authentication. - * @return An instance of HttpCredentialsAdapter initialized with the given configuration. - */ -fun getHttpCredentialsAdapter(configuration: ClientConfiguration): HttpCredentialsAdapter { - require(configuration is KeyTrait) { "Configuration must implement KeyTrait!" } - require(configuration is SecretTrait) { "Configuration must implement SecretTrait!" } - require(configuration is AuthEndpointTrait) { "Configuration must implement AuthEndpointTrait!" } - require(configuration is AuthenticationStrategyTrait) { "Configuration must implement ClientConfigurationTrait!" } - - return CreateSingletonHttpCredentialsAdapter.execute(configuration) -} - -/** - * A singleton class responsible for creating and managing instances of `HttpCredentialsAdapter` - * based on the provided `ClientConfiguration`. This class ensures that each configuration has a - * unique adapter instance. - */ -private class CreateSingletonHttpCredentialsAdapter private constructor() : - (ClientConfiguration) -> HttpCredentialsAdapter { - companion object { - @JvmStatic - private val INSTANCE = CreateSingletonHttpCredentialsAdapter() - - @JvmStatic - private val adapters: ConcurrentMap = ConcurrentHashMap() - - /** - * Invokes the creation or retrieval of an `HttpCredentialsAdapter` based on the provided `ClientConfiguration`. - * - * @param configuration The `ClientConfiguration` instance which must implement `KeyTrait`, `IdTrait`, - * and `AuthenticationStrategyTrait`. - * @return An instance of `HttpCredentialsAdapter` mapped to the unique ID of the provided configuration. - * @throws IllegalArgumentException If the provided configuration does not implement `KeyTrait`. - */ - @JvmStatic - fun execute(configuration: ClientConfiguration): HttpCredentialsAdapter = - INSTANCE(configuration) - } - - /** - * Invokes the creation or retrieval of an `HttpCredentialsAdapter` based on the provided `ClientConfiguration`. - * - * @param configuration The `ClientConfiguration` instance which must implement `KeyTrait`, `IdTrait`, - * and `AuthenticationStrategyTrait`. - * @return An instance of `HttpCredentialsAdapter` mapped to the unique ID of the provided configuration. - * @throws IllegalArgumentException If the provided configuration does not implement `KeyTrait`. - */ - override fun invoke(configuration: ClientConfiguration): HttpCredentialsAdapter { - require(configuration is KeyTrait) { "Configuration must implement KeyTrait!" } - - val strategy = (configuration as AuthenticationStrategyTrait).getAuthenticationStrategy() - val handler = strategy.handlerFactory.createAuthenticationHandler( - config = configuration, - transport = createApacheHttpTransport(configuration) - ) - - return adapters.getOrPut((configuration as IdTrait).id) { - HttpCredentialsAdapter( - OAuth2CredentialsWithRefresh.newBuilder() - .withDefaultConfigurations() - .setRefreshHandler { handler.refreshAccessToken() as AccessToken } - .build() - ) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt deleted file mode 100644 index a1d5b9ff..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClient.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.configuration.ExpediaGroupDefaultClientConfiguration -import com.expediagroup.sdk.core.logging.mask.configureLogMasking -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingBodyFieldsTrait -import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingHeadersTrait -import com.google.api.client.googleapis.services.AbstractGoogleClient -import com.google.api.client.googleapis.services.AbstractGoogleClientRequest - - -/** - * ApiClient class is responsible for making API requests using an extended Google API client. - * - * @param builder The ApiClientBuilder instance used for building the API client. - * @param configuration The client configuration implementing `ClientConfiguration`. - * - * The ApiClient utilizes the provided builder to initialize and configure the API client. It ensures that - * the given configuration adheres to both `MaskedLoggingHeadersTrait` and `MaskedLoggingBodyFieldsTrait`. - * It configures log masking for the headers and body fields as specified by these traits. - */ -class ApiClient( - builder: ApiClientBuilder, - configuration: ClientConfiguration = ExpediaGroupDefaultClientConfiguration -) : AbstractGoogleClient(builder) { - init { - require(configuration is MaskedLoggingHeadersTrait) - require(configuration is MaskedLoggingBodyFieldsTrait) - - configureLogMasking((configuration as MaskedLoggingHeadersTrait).getMaskedLoggingHeaders()) - configureLogMasking((configuration as MaskedLoggingBodyFieldsTrait).getMaskedLoggingBodyFields()) - } - - /** - * Initializes the given HTTP client request with additional configurations. - * - * @param httpClientRequest The HTTP client request to be initialized. - * It can be an instance of `AbstractGoogleClientRequest`. - */ - override fun initialize(httpClientRequest: AbstractGoogleClientRequest<*>?) { - super.initialize(httpClientRequest) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt deleted file mode 100644 index e8c30b8b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientApolloHttpEngine.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.expediagroup.sdk.core.client - -import com.apollographql.apollo.api.http.HttpHeader -import com.apollographql.apollo.api.http.HttpRequest -import com.apollographql.apollo.api.http.HttpResponse -import com.apollographql.apollo.exception.ApolloNetworkException -import com.apollographql.java.client.ApolloDisposable -import com.apollographql.java.client.network.http.HttpCallback -import com.apollographql.java.client.network.http.HttpEngine -import com.expediagroup.sdk.core.request.Request -import com.google.api.client.http.HttpHeaders -import okio.buffer -import okio.source - - -/** - * Implementation of the `HttpEngine` interface that uses an `ApiClient` instance to execute HTTP requests. - * - * @constructor Creates an `ApiClientApolloHttpEngine` with the specified `ApiClient`. - * - * @param client The instance of `ApiClient` used to execute the HTTP requests. - */ -class ApiClientApolloHttpEngine( - private val client: ApiClient -) : HttpEngine { - /** - * Executes an HTTP request using the provided `ApiClient` and handles the response or failure via the specified callback. - * - * @param request The GraphQL HTTP request containing method, URL, body, and headers. - * @param callback The callback to handle the HTTP response or failure. - * @param disposable The disposable resource associated with the request, allowing cancellation if necessary. - */ - override fun execute(request: HttpRequest, callback: HttpCallback, disposable: ApolloDisposable) { - try { - Request( - client, - graphQLRequest = request, - responseType = Any::class.java - ).executeUnparsed().let { response -> - HttpResponse.Builder(statusCode = response.statusCode).apply { - body(response.content.source().buffer()) - headers(response.headers.toApolloHeaders()) - }.also { - callback.onResponse(it.build()) - } - } - } catch (e: Exception) { - callback.onFailure(ApolloNetworkException(platformCause = e)) - } - } - - /** - * Disposes of any resources held by the `ApiClientApolloHttpEngine`. - * - * This implementation is a no-op as there are no resources to be disposed of in this class. - * Typically, this method would be used to clean up resources, such as closing network connections. - */ - override fun dispose() { - // no-op - } - - /** - * Converts `HttpHeaders` into a list of `HttpHeader` objects. - * - * This method iterates through the key-value pairs in the `HttpHeaders`, converting each key-value pair - * into `HttpHeader` instances. - * If the value is an `Iterable`, each item is used to create an `HttpHeader`. - * Otherwise, a single `HttpHeader` with the value is created. - * - * @return A list of `HttpHeader` objects. - */ - private fun HttpHeaders.toApolloHeaders(): List { - val self = this@toApolloHeaders - - return buildList { - self.forEach { key, value -> - when (value) { - is Iterable<*> -> this.addAll(value.map { HttpHeader(key, value.toString()) }) - else -> this.add(HttpHeader(key, value.toString())) - } - } - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt deleted file mode 100644 index 5d54e5f4..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/ApiClientBuilder.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.configuration.SdkMetadata -import com.expediagroup.sdk.core.request.initializer.ApiClientRequestInitializer -import com.google.api.client.googleapis.services.AbstractGoogleClient -import com.google.api.client.http.GenericUrl -import com.google.api.client.http.HttpRequestInitializer -import com.google.api.client.http.HttpTransport -import com.google.api.client.json.JsonFactory - -/** - * Builder for constructing an instance of `ApiClient`. This class extends - * `AbstractGoogleClient.Builder` and provides the necessary configurations - * for the API client. - * - * @param transport The HTTP transport to be used for the API client. - * @param jsonFactory Factory for JSON parsing and serialization. - * @param rootUrl The root URL for the API service. - * @param requestInitializer Optional request initializer for HTTP requests. - * @param servicePath The path to the service endpoint. - */ -class ApiClientBuilder( - transport: HttpTransport, - jsonFactory: JsonFactory, - rootUrl: GenericUrl, - requestInitializer: HttpRequestInitializer? = null, - servicePath: String = "", -) : AbstractGoogleClient.Builder( - transport, - rootUrl.build(), - servicePath, - jsonFactory.createJsonObjectParser(), // TODO: Configure value - requestInitializer, -) { - /** - * Builds an instance of `ApiClient` using the current configuration of `ApiClientBuilder`. - * - * @return An instance of `ApiClient` configured with the properties set in this builder. - */ - override fun build(): AbstractGoogleClient { - return ApiClient(this) - } - - init { - applicationName = SdkMetadata.getArtifactId() - googleClientRequestInitializer = ApiClientRequestInitializer.default() - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/RequestExecutor.kt similarity index 91% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/RequestExecutor.kt index 1b2e617e..39c5b477 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/RequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/RequestExecutor.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.client +package com.expediagroup.sdk.core.client +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response -import com.expediagroup.sdk.core2.interceptor.Interceptor /** * Abstract base class for processing HTTP requests within the SDK. @@ -52,7 +52,7 @@ import com.expediagroup.sdk.core2.interceptor.Interceptor * * @param transport The transport implementation to use for executing requests */ -abstract class RequestExecutor(private val transport: Transport) { +abstract class RequestExecutor(protected val transport: Transport) { /** * List of interceptors to be applied to requests in order. * diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt deleted file mode 100644 index fd5026c0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/SdkClient.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.expediagroup.sdk.core.client - -import com.expediagroup.sdk.core.configuration.FullClientConfiguration -import com.expediagroup.sdk.core.request.Request -import com.expediagroup.sdk.core.client.util.createApiClient -import com.expediagroup.sdk.core.jackson.deserialize -import com.expediagroup.sdk.core.model.Operation -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import java.io.InputStream - -/** - * SdkClient is a wrapper for creating and executing API requests. - * - * @param configuration The client configuration implementing `FullClientConfiguration`. - */ -class SdkClient( - configuration: FullClientConfiguration, -) { - val apiClient: ApiClient = createApiClient( - configuration = configuration, - ) - - /** - * Executes a given operation and deserializes the response into the specified type. - * - * @param T The type into which the response will be deserialized. - * @param operation The operation to be executed. - * @param enableGzipContent Flag to enable or disable GZip content. - * @param typeReference A reference to the type into which the response should be deserialized. - * @return The deserialized response of the operation, or null if the deserialization fails. - */ - inline fun execute( - operation: Operation<*>, - enableGzipContent: Boolean = false, - typeReference: TypeReference = jacksonTypeRef() - ): T? = - Request( - apiClient, - operation, - T::class.java - ).setDisableGZipContent( - enableGzipContent.not() - ).executeUnparsed().let { - deserialize(it.parseAsString(), typeReference) - } - - /** - * Executes a given operation and returns the response as an InputStream. - * - * @param operation The operation to be executed. - * @param enableGzipContent Flag to enable or disable GZip content. - * @return The InputStream representing the response of the operation, or null if the operation fails. - */ - fun executeAsInputStream(operation: Operation<*>, enableGzipContent: Boolean = false): InputStream? = - Request( - apiClient, - operation, - Any::class.java - ).apply { - setDisableGZipContent(enableGzipContent.not()) - }.executeAsInputStream() - - /** - * Executes a given operation without parsing the response and returns the raw result. - * - * @param operation The operation to be executed. - * @param enableGzipContent Flag to enable or disable GZip content. - * @return The raw response of the operation. - */ - fun executeUnparsed(operation: Operation<*>, enableGzipContent: Boolean = false): Any = - Request( - apiClient, - operation, - Any::class.java - ).apply { - setDisableGZipContent(enableGzipContent.not()) - }.executeUnparsed() - - /** - * Executes a given operation and downloads the response to an OutputStream. - * - * @param operation The operation to be executed. - * @param outputStream The OutputStream to which the response will be written. - * @param enableGzipContent Flag to enable or disable GZip content compression. - */ - fun executeAndDownloadTo( - operation: Operation<*>, - outputStream: java.io.OutputStream?, - enableGzipContent: Boolean = false - ) { - val request = Request( - apiClient, - operation, - Any::class.java - ).apply { - setDisableGZipContent(enableGzipContent.not()) - } - - request.executeAndDownloadTo(outputStream) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Transport.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/client/Transport.kt index 7119f02b..4de0446c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/client/Transport.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Transport.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.client +package com.expediagroup.sdk.core.client +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response /** * A transport layer interface that adapts different HTTP client libraries to work with the SDK. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt deleted file mode 100644 index cd8c75eb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/ApiClientUtil.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.expediagroup.sdk.core.client.util - -import com.expediagroup.sdk.core.apache.util.getSingletonApacheHttpTransport -import com.expediagroup.sdk.core.authentication.util.getHttpCredentialsAdapter -import com.expediagroup.sdk.core.client.ApiClient -import com.expediagroup.sdk.core.client.ApiClientBuilder -import com.expediagroup.sdk.core.request.initializer.SdkRequestInitializer -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.expediagroup.sdk.core.trait.configuration.EndpointTrait -import com.google.api.client.http.GenericUrl -import com.google.api.client.http.HttpTransport - -/** - * Creates an instance of `ApiClient` using the provided client configuration, and optional HTTP transport. - * - * @param configuration The client configuration implementing `ClientConfiguration` interface. Must also implement `EndpointTrait`. - * @param transport An optional `HttpTransport` object. If not provided, a default transport will be used. - * @return An instance of `ApiClient`. - */ -@JvmOverloads -fun createApiClient( - configuration: ClientConfiguration, - transport: HttpTransport? = null -): ApiClient { - val jsonFactory = createGsonFactory() - require(configuration is EndpointTrait) { "Configuration must implement EndpointTrait" } - - val requestInitializer = SdkRequestInitializer.default() - .add(getHttpCredentialsAdapter(configuration)) - - val builder = ApiClientBuilder( - transport = transport ?: getSingletonApacheHttpTransport(configuration), - jsonFactory = jsonFactory, - rootUrl = GenericUrl((configuration as EndpointTrait).getEndpoint()), - requestInitializer = requestInitializer, - ) - - return ApiClient(builder, configuration) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt deleted file mode 100644 index 99fca2b8..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/client/util/GsonFactoryUtil.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.expediagroup.sdk.core.client.util - -import com.google.api.client.json.gson.GsonFactory - -/** - * Creates a GsonFactory instance using the provided builder or returns the default instance if the builder is null. - * - * TODO: To be extended to hold Gson Factory configuration - * - * @param builder An optional builder for customizing the GsonFactory instance. If null, the default GsonFactory instance is returned. - * @return An instance of GsonFactory based on the builder configuration or the default configuration. - */ -internal fun createGsonFactory(builder: GsonFactory.Builder? = null) = - if (builder == null) { - GsonFactory.getDefaultInstance() - } else { - builder.build() - } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt deleted file mode 100644 index 42aee5e0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/DefaultClientBuilder.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.expediagroup.sdk.core.configuration - - -/** - * A builder class for constructing instances of FullClientConfiguration with customizable parameters. - * This builder pattern allows you to configure various settings for the client, such as authentication details, - * endpoints, and timeout settings. - * - * @param T The type of the client that will be built. - */ -abstract class DefaultClientBuilder { - private var configurationBuilder = FullClientConfiguration.Builder() - - fun key(key: String) = apply { - configurationBuilder = configurationBuilder.key(key) - } - - fun secret(secret: String) = apply { - configurationBuilder = configurationBuilder.secret(secret) - } - - fun endpoint(endpoint: String) = apply { - configurationBuilder = configurationBuilder.endpoint(endpoint) - } - - fun authEndpoint(authEndpoint: String) = apply { - configurationBuilder = configurationBuilder.authEndpoint(authEndpoint) - } - - fun requestTimeout(requestTimeout: Long) = apply { - configurationBuilder = configurationBuilder.requestTimeout(requestTimeout) - } - - fun connectionTimeout(connectionTimeout: Long) = apply { - configurationBuilder = configurationBuilder.connectionTimeout(connectionTimeout) - } - - fun socketTimeout(socketTimeout: Long) = apply { - configurationBuilder = configurationBuilder.socketTimeout(socketTimeout) - } - - fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { - configurationBuilder = configurationBuilder.maskedLoggingHeaders(maskedLoggingHeaders) - } - - fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { - configurationBuilder = configurationBuilder.maskedLoggingBodyFields(maskedLoggingBodyFields) - } - - fun maxConnectionTotal(maxConnectionTotal: Int) = apply { - configurationBuilder = configurationBuilder.maxConnectionsTotal(maxConnectionTotal) - } - - fun maxConnectionPerRoute(maxConnectionPerRoute: Int) = apply { - configurationBuilder = configurationBuilder.maxConnectionsPerRoute(maxConnectionPerRoute) - } - - /** - * Builds and returns a fully configured client instance. - * - * @return a fully configured instance of the client. - */ - fun buildConfiguration(): FullClientConfiguration { - return configurationBuilder.build() - } - - /** - * Builds and returns a fully configured client instance. - * - * @return a fully configured instance of the client. - */ - open fun build(): T { - throw NotImplementedError("Not yet implemented") - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt deleted file mode 100644 index 4f01e3a8..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupDefaultClientConfiguration.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.expediagroup.sdk.core.configuration - -import com.expediagroup.sdk.core.constant.Constant -import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy - -/** - * Implementation of `FullClientConfiguration` that provides default configuration values for the Expedia Group API client. - * - * This object acts as the default configuration and returns preset values for various configuration traits needed - * to interact with the Expedia Group API. - */ -object ExpediaGroupDefaultClientConfiguration : - FullClientConfiguration { - - override fun getKey(): String { - throw NotImplementedError("Not implemented") - } - - override fun getSecret(): String { - throw NotImplementedError("Not implemented") - } - - override fun getEndpoint(): String = "https://api.expediagroup.com/" - override fun getAuthEndpoint(): String = "${getEndpoint()}identity/oauth2/v3/token/" - override fun getAuthenticationStrategy(): AuthenticationStrategy = AuthenticationStrategy.BEARER - override fun getRequestTimeout(): Long = Constant.ONE_HOUR_IN_MILLIS - override fun getConnectionTimeout(): Long = Constant.TEN_SECONDS_IN_MILLIS - override fun getSocketTimeout(): Long = Constant.FIFTEEN_SECONDS_IN_MILLIS - override fun getMaxConnectionsTotal(): Int = Constant.MAX_CONNECTIONS_TOTAL - override fun getMaxConnectionsPerRoute(): Int = Constant.MAX_CONNECTIONS_PER_ROUTE - override fun getMaskedLoggingHeaders(): Set = setOf("Authorization") - override fun getMaskedLoggingBodyFields(): Set = setOf( - "cvv", - "pin", - "card_cvv", - "card_cvv2", - "card_number", - "access_token", - "security_code", - "account_number", - "card_avs_response", - "card_cvv_response", - "card_cvv2_response", - ) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt deleted file mode 100644 index f8c8e081..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/FullClientConfiguration.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.expediagroup.sdk.core.configuration - -import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupConfigurationException -import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy -import com.expediagroup.sdk.core.trait.common.BuilderTrait -import com.expediagroup.sdk.core.trait.configuration.KeyTrait -import com.expediagroup.sdk.core.trait.configuration.SecretTrait -import com.expediagroup.sdk.core.trait.configuration.EndpointTrait -import com.expediagroup.sdk.core.trait.configuration.AuthEndpointTrait -import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingHeadersTrait -import com.expediagroup.sdk.core.trait.configuration.MaskedLoggingBodyFieldsTrait -import com.expediagroup.sdk.core.trait.configuration.RequestTimeoutTrait -import com.expediagroup.sdk.core.trait.configuration.SocketTimeoutTrait -import com.expediagroup.sdk.core.trait.configuration.ConnectionTimeoutTrait -import com.expediagroup.sdk.core.trait.configuration.AuthenticationStrategyTrait -import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsTotalTrait -import com.expediagroup.sdk.core.trait.configuration.MaxConnectionsPerRouteTrait -import java.util.UUID - -/** - * Interface representing the full configuration required for a client setup. - * Combines various traits that offer specific configuration aspects such as key, secret, endpoints, - * timeout settings, logging preferences, and connection limitations. - */ -interface FullClientConfiguration : - KeyTrait, - SecretTrait, - EndpointTrait, - AuthEndpointTrait, - MaskedLoggingHeadersTrait, - MaskedLoggingBodyFieldsTrait, - RequestTimeoutTrait, - SocketTimeoutTrait, - ConnectionTimeoutTrait, - AuthenticationStrategyTrait, - MaxConnectionsTotalTrait, - MaxConnectionsPerRouteTrait { - open class Builder : BuilderTrait { - private var key: String? = null - private var secret: String? = null - private var endpoint: String? = null - private var authEndpoint: String? = null - private var requestTimeout: Long? = null - private var connectionTimeout: Long? = null - private var socketTimeout: Long? = null - private var maskedLoggingHeaders: Set? = null - private var maskedLoggingBodyFields: Set? = null - private var maxConnectionsTotal: Int? = null - private var maxConnectionsPerRoute: Int? = null - private var authenticationStrategy: String? = null - - fun key(key: String) = apply { - this.key = key - } - - fun secret(secret: String) = apply { - this.secret = secret - } - - fun endpoint(endpoint: String) = apply { - this.endpoint = endpoint - } - - fun authEndpoint(authEndpoint: String) = apply { - this.authEndpoint = authEndpoint - } - - fun requestTimeout(requestTimeout: Long) = apply { - this.requestTimeout = requestTimeout - } - - fun connectionTimeout(connectionTimeout: Long) = apply { - this.connectionTimeout = connectionTimeout - } - - fun socketTimeout(socketTimeout: Long) = apply { - this.socketTimeout = socketTimeout - } - - fun maskedLoggingHeaders(maskedLoggingHeaders: Set) = apply { - this.maskedLoggingHeaders = maskedLoggingHeaders - } - - fun maskedLoggingBodyFields(maskedLoggingBodyFields: Set) = apply { - this.maskedLoggingBodyFields = maskedLoggingBodyFields - } - - fun maxConnectionsTotal(maxConnectionsTotal: Int) = apply { - this.maxConnectionsTotal = maxConnectionsTotal - } - - fun maxConnectionsPerRoute(maxConnectionsPerRoute: Int) = apply { - this.maxConnectionsPerRoute = maxConnectionsPerRoute - } - - fun authenticationStrategy(authenticationStrategy: String) = apply { - this.authenticationStrategy = authenticationStrategy - } - - /** - * Builds and returns a fully configured instance of `FullClientConfiguration`. - * - * @return A `FullClientConfiguration` object with all required settings and defaults applied. - * @throws ExpediaGroupConfigurationException if required settings like key or secret are missing. - */ - override fun build(): FullClientConfiguration = - object : FullClientConfiguration { - override val id: UUID = UUID.randomUUID() - - override fun getKey(): String = - key ?: throw ExpediaGroupConfigurationException("API key is required for authentication.") - - - override fun getSecret(): String = - secret ?: throw ExpediaGroupConfigurationException("API secret is required for authentication.") - - override fun getEndpoint(): String = - endpoint ?: ExpediaGroupDefaultClientConfiguration.getEndpoint() - - override fun getAuthEndpoint(): String = - authEndpoint ?: ExpediaGroupDefaultClientConfiguration.getAuthEndpoint() - - override fun getMaskedLoggingHeaders(): Set = - maskedLoggingHeaders ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingHeaders() - - override fun getMaskedLoggingBodyFields(): Set = - maskedLoggingBodyFields ?: ExpediaGroupDefaultClientConfiguration.getMaskedLoggingBodyFields() - - override fun getRequestTimeout(): Long = - requestTimeout ?: ExpediaGroupDefaultClientConfiguration.getRequestTimeout() - - override fun getSocketTimeout(): Long = - socketTimeout ?: ExpediaGroupDefaultClientConfiguration.getSocketTimeout() - - override fun getConnectionTimeout(): Long = - connectionTimeout ?: ExpediaGroupDefaultClientConfiguration.getConnectionTimeout() - - override fun getAuthenticationStrategy(): AuthenticationStrategy = - ExpediaGroupDefaultClientConfiguration.getAuthenticationStrategy() - - override fun getMaxConnectionsTotal(): Int = - maxConnectionsTotal ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsTotal() - - override fun getMaxConnectionsPerRoute(): Int = - maxConnectionsPerRoute ?: ExpediaGroupDefaultClientConfiguration.getMaxConnectionsPerRoute() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt deleted file mode 100644 index 3e4669df..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/SdkMetadata.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.expediagroup.sdk.core.configuration - -import java.util.jar.Manifest - -internal object SdkMetadata { - private var artifactId: String - private var version: String - private var userAgentPrefix: String - - fun getArtifactId(): String = artifactId - fun getVersion(): String = version - fun getUserAgentPrefix(): String = userAgentPrefix - - init { - this::class.java.classLoader.getResourceAsStream("META-INF/MANIFEST.MF").use { - Manifest(it).apply { - artifactId = mainAttributes.getValue("artifactId") - version = mainAttributes.getValue("version") - userAgentPrefix = mainAttributes.getValue("userAgentPrefix") - } - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt deleted file mode 100644 index ed3d676e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.expediagroup.sdk.core.constant - -object Authentication { - const val BEARER_EXPIRY_MARGIN_IN_SECONDS: Long = 10 -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt deleted file mode 100644 index c64e10af..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object Constant { - const val NEWLINE = "\n" - const val COMMA_SPACE = ", " - const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" - const val TEN_SECONDS_IN_MILLIS = 10_0000L - const val FIFTEEN_SECONDS_IN_MILLIS = 150_000L - const val ONE_HOUR_IN_MILLIS = 3_600_000L - - private const val SUCCESSFUL_STATUS_CODES_RANGE_START = 200 - private const val SUCCESSFUL_STATUS_CODES_RANGE_END = 299 - val SUCCESSFUL_STATUS_CODES_RANGE: IntRange = SUCCESSFUL_STATUS_CODES_RANGE_START..SUCCESSFUL_STATUS_CODES_RANGE_END - - const val MAX_CONNECTIONS_TOTAL: Int = 500 - const val MAX_CONNECTIONS_PER_ROUTE: Int = 100 -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ExceptionMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ExceptionMessage.kt deleted file mode 100644 index 33220440..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ExceptionMessage.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object ExceptionMessage { - const val AUTHENTICATION_FAILURE = "Unable to authenticate" - - const val AUTHENTICATION_NOT_CONFIGURED_FOR_CLIENT = "Authentication is not configured" - - const val LOGGING_MASKED_FIELDS_NOT_CONFIGURED_FOR_CLIENT = "Logging masked fields is not configured" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderKey.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderKey.kt deleted file mode 100644 index d3a72279..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderKey.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object HeaderKey { - const val PAGINATION_TOTAL_RESULTS = "pagination-total-results" - - const val LINK = "link" - - const val TRANSACTION_ID = "transaction-id" - - const val X_SDK_TITLE = "x-sdk-title" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderValue.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderValue.kt deleted file mode 100644 index 3c418bc7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/HeaderValue.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object HeaderValue { - const val GZIP = "gzip" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Key.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Key.kt deleted file mode 100644 index a7a71ca1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Key.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object Key { - const val CLIENT_KEY = "client_key" - - const val CLIENT_SECRET = "client_secret" - - const val ENDPOINT = "endpoint" - - const val AUTH_ENDPOINT = "auth_endpoint" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt deleted file mode 100644 index 9c7e35d1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingFields.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -import com.google.auth.http.AuthHttpConstants - -internal data object LogMaskingFields { - val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(AuthHttpConstants.AUTHORIZATION) - val DEFAULT_MASKED_BODY_FIELDS: Set = - setOf( - "cvv", - "pin", - "card_cvv", - "card_cvv2", - "card_number", - "access_token", - "security_code", - "account_number", - "card_avs_response", - "card_cvv_response", - "card_cvv2_response", - "verificationNumber", - "vatNumber" - ) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggerName.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggerName.kt deleted file mode 100644 index 749fcd3f..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggerName.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object LoggerName { - const val REQUEST_BODY_LOGGER: String = "RequestBodyLogger" - const val RESPONSE_BODY_LOGGER: String = "ResponseBodyLogger" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt deleted file mode 100644 index 00c25226..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant - -internal object LoggingMessage { - const val LOGGING_PREFIX = "ExpediaSDK:" - - const val TOKEN_RENEWAL_IN_PROGRESS = "Renewing token" - - const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" - - const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" - - const val OMITTED = "<-- omitted -->" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.kt deleted file mode 100644 index 316ef390..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant.provider - -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider.getTransactionIdMessage - -internal object ExceptionMessageProvider { - fun getMissingRequiredConfigurationMessage(name: String): String = "Missing required configuration: $name" - - fun getExceptionOccurredWithTransactionIdMessage( - transactionId: String?, - message: String? - ): String = "Exception occurred" + getTransactionIdMessage(transactionId) + getConcatenatedMessage(message) - - private fun getConcatenatedMessage(message: String?) = if (message != null) ": $message" else "" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt deleted file mode 100644 index a1db51eb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.constant.provider - -import com.expediagroup.sdk.core.http.HttpStatus - - -internal object LoggingMessageProvider { - fun getTokenExpiresInMessage(expiresIn: Int) = "New token expires in $expiresIn seconds" - - fun getResponseUnsuccessfulMessage( - httpStatusCode: HttpStatus, - transactionId: String? - ) = "Unsuccessful response [$httpStatusCode]${getTransactionIdMessage(transactionId)}" - - fun getChosenProviderMessage( - property: String, - providerName: String - ) = "Successfully loaded [$property] from [$providerName]" - - fun getRuntimeConfigurationProviderMessage( - property: String, - value: T - ) = "Setting [$property] to [$value] from runtime configuration provider" - - fun getResponseBodyMessage( - body: String, - transactionId: String? - ) = "Response Body${getTransactionIdMessage(transactionId)}: $body" - - fun getRequestBodyMessage( - body: String, - transactionId: String? - ) = "Request Body${getTransactionIdMessage(transactionId)}: $body" - - fun getTransactionIdMessage(transactionId: String?) = if (transactionId != null) " for transaction-id [$transactionId]" else "" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt index 855584e0..20a1f910 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt @@ -1,9 +1,13 @@ package com.expediagroup.sdk.core.extension +import java.nio.charset.Charset + inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { return this ?: throw exceptionProvider() } -fun Boolean?.orFalseIfNull(): Boolean = this ?: false +fun Boolean?.orFalseIfNull(): Boolean = this == true fun String?.orNullIfBlank(): String? = this?.takeUnless { it.isBlank() } + +fun Charset?.orUtf8() = this ?: Charsets.UTF_8 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt deleted file mode 100644 index ec873abf..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/BlobTypeDetector.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.expediagroup.sdk.core.http - -import org.apache.tika.Tika - -/** - * A utility class for detecting the type of binary large objects (BLOBs) using Apache Tika. - * - * This class is a singleton, which means it restricts the instantiation to one object. - * It inherits from the Tika class provided by Apache Tika library. - */ -class BlobTypeDetector private constructor(): Tika() { - companion object { - @JvmStatic - private val tika = Tika() - - @JvmStatic - fun getInstance(): BlobTypeDetector { - return BlobTypeDetector() - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt index 12e0f59d..d42e7b69 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/ContentType.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.http +package com.expediagroup.sdk.core.http -import com.expediagroup.sdk.core2.http.ContentType.entries +import com.expediagroup.sdk.core.http.ContentType.entries /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt index 6ced1c2b..cd806413 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/http/Headers.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.http +package com.expediagroup.sdk.core.http import java.util.Locale @@ -173,7 +173,7 @@ class Headers private constructor(private val headersMap: Map deserialize(json: String, typeReference: TypeReference): T { - return OBJECT_MAPPER.readValue(json, typeReference) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt deleted file mode 100644 index f8dc6fc1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLogger.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.logging - -import com.expediagroup.sdk.core.constant.Constant -import com.expediagroup.sdk.core.constant.LoggingMessage.LOGGING_PREFIX -import com.expediagroup.sdk.core.logging.mask.maskLogs -import org.slf4j.Logger - -/** - * The ExpediaGroupLogger class is a decorator for the Logger interface that enhances log messages - * with additional formatting and tagging functionality. - * - * @param logger The underlying Logger instance to delegate logging actions to. - */ -internal class ExpediaGroupLogger(private val logger: Logger) : Logger by logger { - override fun info(msg: String) = logger.info(decorate(msg)) - - fun info(msg: String, vararg tags: LogMessageTag) = logger.info(decorate(msg, tags.toSet())) - - override fun warn(msg: String) = logger.warn(decorate(msg)) - - fun warn(msg: String, vararg tags: LogMessageTag) = logger.warn(decorate(msg, tags.toSet())) - - override fun debug(msg: String) = logger.debug(decorate(msg)) - - fun debug(msg: String, vararg tags: LogMessageTag) = logger.debug(decorate(msg, tags.toSet())) - - override fun error(msg: String) = logger.error(decorate(msg)) - - fun error(msg: String, vararg tags: LogMessageTag) = logger.error(decorate(msg, tags.toSet())) - - /** - * Normalizes a log message by applying specific formatting and tagging. - * - * @param msg The log message to normalize. - * @param tags A set of tags to include in the normalized message. - * @return A formatted and tagged log message. - */ - private fun normalize(msg: String, tags: Set = emptySet()): String = - buildList { - maskLogs(msg).trim().split(Constant.NEWLINE).forEach { line -> - tags.joinToString(Constant.COMMA_SPACE).let { tagsPrefix -> - if (tagsPrefix.isNotBlank()) "[$tagsPrefix]" else "" - }.also { - add("${Constant.DOUBLE_RIGHT_ANGLE_BRACKETS} $it $line".trim()) - } - } - }.joinToString(Constant.NEWLINE) - - /** - * Decorates a log message by prefixing it with a logging prefix and normalizing its format using provided tags. - * - * @param msg The message to be decorated. - * @param tags A set of tags to include in the decorated message. Defaults to an empty set. - * @return The decorated log message with the logging prefix and normalized format. - */ - private fun decorate(msg: String, tags: Set = emptySet()): String = "$LOGGING_PREFIX\n${normalize(msg, tags)}".trim() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt deleted file mode 100644 index 56881abf..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/ExpediaGroupLoggerFactory.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.logging - -import org.slf4j.LoggerFactory - -/** - * Factory object for creating instances of ExpediaGroupLogger. - */ -internal object ExpediaGroupLoggerFactory { - fun getLogger(clazz: Class<*>) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz)) - - fun getLogger( - clazz: Class<*>, - client: Any - ) = ExpediaGroupLogger(LoggerFactory.getLogger(clazz)) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt deleted file mode 100644 index 408aab63..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageConstants.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.expediagroup.sdk.core.logging - -/** - * Object holding constants for logging messages related to HTTP requests and responses. - */ -object LogMessageConstant { - const val REQUEST_HEADERS: String = "Request Headers:" - - const val REQUEST_BODY: String = "Request Body:" - - const val RESPONSE_HEADERS: String = "Response Headers:" - - const val RESPONSE_BODY: String = "Response Body:" - - const val EMPTY_OR_UNKNOWN_RESPONSE_BODY: String = "Empty response body or unknown content length" - - const val BODY_CONTENT_TYPE_NOT_SUPPORTED = "Content type %s not supported for logging" - - const val RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE = "Response too large to be logged whole..." - - const val RESPONSE_CONTENT_INPUT_STREAM_DOES_NOT_SUPPORT_MARK = - "Response content input stream does not support mark" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt similarity index 91% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 2da6151b..3fe4e641 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -1,10 +1,10 @@ -package com.expediagroup.sdk.core2.logging +package com.expediagroup.sdk.core.logging -import com.expediagroup.sdk.core2.http.RequestBody -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response -import com.expediagroup.sdk.core2.interceptor.Interceptor -import com.expediagroup.sdk.core2.logging.common.LoggerDecorator +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.RequestBody +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.interceptor.Interceptor +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import java.io.IOException import java.nio.charset.Charset import okio.Buffer diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt similarity index 94% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt index c4a1c676..e26fd127 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/Constant.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.core2.logging.common +package com.expediagroup.sdk.core.logging.common object Constant { const val NEWLINE = "\n" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt similarity index 88% rename from code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt index 6c11f15a..81f88cfe 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LogMessageTag.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.core.logging +package com.expediagroup.sdk.core.logging.common /** * Enumeration representing the different tags available for log messages. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt similarity index 88% rename from code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt index bac40449..aefef462 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggableContentTypes.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt @@ -1,6 +1,6 @@ -package com.expediagroup.sdk.core.logging +package com.expediagroup.sdk.core.logging.common -import org.apache.http.entity.ContentType +import com.expediagroup.sdk.core.http.ContentType /** * A list of MIME types representing content types that are deemed loggable. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt similarity index 96% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt index c6fa8bc4..b996b3bf 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggerDecorator.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.logging.common +package com.expediagroup.sdk.core.logging.common -import com.expediagroup.sdk.core2.logging.masking.maskLogs +import com.expediagroup.sdk.core.logging.masking.maskLogs import org.slf4j.Logger /** diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt deleted file mode 100644 index aabdfbc7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldFilter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.expediagroup.sdk.core.logging.mask - -import com.ebay.ejmask.core.BaseFilter - -/** - * A filter class that extends the BaseFilter to apply masking on specific JSON fields using - * `ExpediaGroupJsonFieldPatternBuilder` for pattern building. - * - * This filter helps in masking sensitive JSON fields by replacing them with a predefined pattern. - * - * @constructor - * Initializes ExpediaGroupJsonFieldFilter with the specified fields to be masked. - * - * @param maskedFields An array of strings representing the names of the fields to be masked. - */ -internal class ExpediaGroupJsonFieldFilter(maskedFields: Array) : BaseFilter( - ExpediaGroupJsonFieldPatternBuilder::class.java, - *maskedFields -) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt deleted file mode 100644 index 6315aa01..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/ExpediaGroupJsonFieldPatternBuilder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.expediagroup.sdk.core.logging.mask - -import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder -import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED - -/** - * A builder class for creating JSON field replacement patterns specifically for Expedia Group. - * - * This class extends the `JsonFieldPatternBuilder` and provides an implementation for building - * replacement patterns for JSON field masking. - * - * The replacement pattern format generated by this builder is structured to conceal sensitive - * data while keeping a specified number of characters visible. - */ -internal class ExpediaGroupJsonFieldPatternBuilder : JsonFieldPatternBuilder() { - override fun buildReplacement(visibleCharacters: Int, vararg fieldNames: String?): String = "\"$1$2$OMITTED\"" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt deleted file mode 100644 index e108a587..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/mask/MaskLogs.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.expediagroup.sdk.core.logging.mask - -import com.ebay.ejmask.core.BaseFilter -import com.ebay.ejmask.core.EJMask -import com.ebay.ejmask.core.EJMaskInitializer -import com.ebay.ejmask.core.util.LoggerUtil - -/** - * Masks sensitive information within the provided log string. - * - * @param logs The log string that may contain sensitive information requiring masking. - * @return A new log string with sensitive information masked. - */ -fun maskLogs(logs: String): String { - return MaskLogs.execute(logs) -} - -/** - * Configures log masking by adding specified fields to the mask list. - * - * This function integrates with the log masking system to include additional fields - * that should be masked in the logs. The fields provided in the parameter are added - * to the set of fields that will have their values masked when logs are generated. - * - * @param fields The set of field names that need to be masked in the logs. - */ -fun configureLogMasking(fields: Set) { - MaskLogs.addFields(fields) -} - -/** - * Checks if a specified field is among the fields that should be masked. - * - * @param field The name of the field to check. - * @return `true` if the field should be masked, `false` otherwise. - */ -fun isMaskedField(field: String): Boolean { - return MaskLogs.maskedFields.contains(field) -} - -/** - * A utility class for masking sensitive information in log strings. - * - * The `MaskLogs` class is designed to replace sensitive information within logs with masked values. - * The class implements the `Function1` interface, enabling it to be invoked with a log string - * to produce a masked version of the string. - * - * The masking process relies on predefined filters that determine which fields within the log - * should be masked. Filters can be added and configured using the companion object's methods. - */ -private class MaskLogs : (String) -> String { - companion object { - @JvmStatic - val filters: MutableList = mutableListOf() - - val maskedFields: MutableSet = mutableSetOf() - - @JvmStatic - val INSTANCE = MaskLogs() - - /** - * Executes the masking process on the provided log string. - * - * @param logs The log string that may contain sensitive information requiring masking. - */ - @JvmStatic - fun execute(logs: String) = INSTANCE(logs) - - /** - * Adds specified fields to the list of fields to be masked in logs. - * - * The fields provided in the parameter are added to the internal set of fields - * and corresponding filters are created and added to the filter list. These filters - * are then integrated into the masking system to ensure the specified fields are - * masked in any logs they appear in. - * - * @param fields The set of field names that need to be masked in the logs. - */ - @JvmStatic - fun addFields(fields: Set) { - maskedFields.addAll(fields) - filters.add(ExpediaGroupJsonFieldFilter(fields.toTypedArray())) - filters.forEach { EJMaskInitializer.addFilter(it) } - } - } - - init { - LoggerUtil.register { _, _, _ -> /* disable logging */ } - } - - /** - * Masks the given text using the EJMask utility. - * - * @param text The input text that needs to be masked. - * @return The masked version of the input text. - */ - override fun invoke(text: String): String { - return EJMask.mask(text) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt similarity index 92% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt index 1be96df7..b36f1b2a 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldFilter.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldFilter.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.core2.logging.masking +package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.core.BaseFilter diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt similarity index 85% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt index cd31a4d9..82543b08 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/JsonFieldPatternBuilder.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/JsonFieldPatternBuilder.kt @@ -1,7 +1,7 @@ -package com.expediagroup.sdk.core2.logging.masking +package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder -import com.expediagroup.sdk.core2.logging.common.Constant.OMITTED +import com.expediagroup.sdk.core.logging.common.Constant.OMITTED /** * A builder class for creating JSON field replacement patterns specifically for Expedia Group. diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt index 3fc82ff6..eb2dfd51 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/masking/MaskLogsUtils.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/masking/MaskLogsUtils.kt @@ -1,4 +1,4 @@ -package com.expediagroup.sdk.core2.logging.masking +package com.expediagroup.sdk.core.logging.masking import com.ebay.ejmask.core.BaseFilter import com.ebay.ejmask.core.EJMask diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Nothing.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Nothing.kt deleted file mode 100644 index bd6863c4..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Nothing.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -/** - * A representation of nothingness. Philosophers have debated the existence of nothing for centuries, but we have finally found it. - */ -data object Nothing diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt deleted file mode 100644 index 4da095cb..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -import com.google.api.client.http.HttpContent -import com.expediagroup.sdk.core.model.TransactionId - -abstract class Operation( - val url: String, - val method: String, - val operationId: String, - val requestBody: T?, - val params: OperationParams?, - val isUpload: Boolean = false, -) { - var transactionId: TransactionId = TransactionId() - private set - - open fun getHttpContent(): HttpContent? = null -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt deleted file mode 100644 index be61cace..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -interface OperationParams { - fun getHeaders(): Map? - fun getQueryParams(): Map>? - fun getPathParams(): Map -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Properties.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Properties.kt deleted file mode 100644 index 5c93b999..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Properties.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -import java.io.BufferedReader -import java.io.InputStreamReader -import java.net.URL - -/** A model of "*.properties" file with some handy methods. */ -class Properties(private val data: Map) { - companion object { - /** Creates a new SdkProperties with the given data. */ - fun from(path: URL) = - Properties( - java.util.Properties().apply { - load(BufferedReader(InputStreamReader(path.openStream()))) - }.map { it.key.toString() to it.value.toString() }.toMap() - ) - } - - /** Returns the data for a given [key]. */ - operator fun get(key: String): String? = data[key] -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt deleted file mode 100644 index c024d35a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:Suppress("unused") - -package com.expediagroup.sdk.core.model - -import java.util.stream.Collectors -import kotlin.collections.Map.Entry - -/** - * A Generic Response to represent the response from a service call. - * - * @property statusCode The HTTP status code of the response - * @property body The body of the response - * @property headers The headers of the response - */ - -open class Response( - val statusCode: Int, - val body: T, - val headers: Map> -) { - constructor(statusCode: Int, data: T, headers: Set>>) : this( - statusCode, - data, - toHeadersMap(headers) - ) - - companion object { - @JvmStatic - fun toHeadersMap(headers: Set>>): Map> = - headers.stream().collect( - Collectors.toMap( - Entry>::key, - Entry>::value - ) - ) - } - - override fun toString() = "Response(statusCode=$statusCode, data=$body, headers=$headers)" -} - -class EmptyResponse( - statusCode: Int, - headers: Map> -) : Response(statusCode, Nothing, headers) { - constructor(statusCode: Int, headers: Set>>) : this(statusCode, toHeadersMap(headers)) - - override fun toString(): String = "EmptyResponse(statusCode=$statusCode, headers=$headers)" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt deleted file mode 100644 index d5a6e46d..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model - -import java.util.UUID - -class TransactionId { - private var transactionId: UUID = UUID.randomUUID() - - fun peek(): UUID { - return transactionId - } - - fun dequeue(): UUID { - return transactionId.also { transactionId = UUID.randomUUID() } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt deleted file mode 100644 index e8c7af85..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/UserAgent.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.expediagroup.sdk.core.model - -import com.expediagroup.sdk.core.configuration.SdkMetadata - -/** - * Data class representing a user agent which contains information about the Java Development Kit (JDK) version, - * operating system, SDK namespace, and SDK version. - * - * @param jdkVersion the version of the Java Development Kit (JDK) being used. - * @param operatingSystem the operating system on which the application is running. - */ -data class UserAgent( - val jdkVersion: String, - val operatingSystem: String, -) { - private val userAgentPrefix: String = SdkMetadata.getUserAgentPrefix() - private val version: String = SdkMetadata.getVersion() - - override fun toString(): String = - "$userAgentPrefix/$version ($jdkVersion; $operatingSystem)" -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt deleted file mode 100644 index c971e54c..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.expediagroup.sdk.core.model.exception.client - -/** - * Thrown to indicate that one or more passed field names are invalid. - * - * @param invalidFields the names of the invalid fields. - */ -class ExpediaGroupInvalidFieldNameException(invalidFields: Collection) : - ExpediaGroupClientException( - "All fields names must contain only alphanumeric characters in addition to - and _ but found [${ - invalidFields.joinToString( - "," - ) - }]" - ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt deleted file mode 100644 index 229fa966..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.core.model.exception.service - -import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider -import com.expediagroup.sdk.core.http.HttpStatus -import com.google.api.client.http.HttpResponse - -abstract class ExpediaGroupApiException(val status: HttpStatus, open val errorObject: Any, transactionId: String?) : - ExpediaGroupServiceException("Unsuccessful response code [${status.code}]${ - LoggingMessageProvider.getTransactionIdMessage( - transactionId - ) - }${stringifyErrorObject(errorObject.toString())}", transactionId = transactionId) { - constructor(response: HttpResponse) : this( - HttpStatus.fromCode(response.statusCode), - response.parseAsString(), - response.request.headers.getFirstHeaderStringValue("transaction-id") - ) - } - -private fun stringifyErrorObject(stringValue: String): String = if (stringValue.isBlank()) " with an empty response body" else ": $stringValue" diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt index e3fcd463..63de302c 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupAuthException.kt @@ -15,7 +15,7 @@ */ package com.expediagroup.sdk.core.model.exception.service -import com.expediagroup.sdk.core.http.HttpStatus +import com.expediagroup.sdk.core.http.Status /** * An exception that is thrown when an authentication error occurs. @@ -36,7 +36,7 @@ class ExpediaGroupAuthException( * @param message The error message. */ constructor( - status: HttpStatus, + status: Status, message: String, ) : this(message = "[${status.code}] $message") @@ -49,5 +49,5 @@ class ExpediaGroupAuthException( constructor( status: Int, message: String, - ) : this(HttpStatus.fromCode(status), message) + ) : this(Status.fromCode(status), message) } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt similarity index 97% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt index fe045f80..ca8f1017 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/BaseOkHttpClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.okhttp +package com.expediagroup.sdk.core.okhttp import java.time.Duration import okhttp3.OkHttpClient diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt similarity index 98% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt index 9a88f1cd..8ea26ff2 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.okhttp +package com.expediagroup.sdk.core.okhttp import okhttp3.ConnectionPool import okhttp3.Interceptor diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt similarity index 83% rename from code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt rename to code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt index 892e1ed3..1195aa08 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/okhttp/OkHttpTransport.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.expediagroup.sdk.core2.okhttp +package com.expediagroup.sdk.core.okhttp -import com.expediagroup.sdk.core2.client.Transport -import com.expediagroup.sdk.core2.http.ResponseBody.Companion.create -import com.expediagroup.sdk.core2.http.MediaType.Companion.parse -import com.expediagroup.sdk.core2.http.Protocol +import com.expediagroup.sdk.core.client.Transport +import com.expediagroup.sdk.core.http.MediaType.Companion.parse +import com.expediagroup.sdk.core.http.Protocol +import com.expediagroup.sdk.core.http.ResponseBody.Companion.create import java.io.IOException import okhttp3.Headers import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -38,12 +38,12 @@ typealias OkHttpResponseBody = ResponseBody typealias OkHttpHeaders = Headers typealias OkHttpHeadersBuilder = Headers.Builder -typealias SdkRequest = com.expediagroup.sdk.core2.http.Request -typealias SdkRequestBody = com.expediagroup.sdk.core2.http.RequestBody -typealias SdkResponse = com.expediagroup.sdk.core2.http.Response -typealias SdkResponseBuilder = com.expediagroup.sdk.core2.http.Response.Builder -typealias SdkHeaders = com.expediagroup.sdk.core2.http.Headers -typealias SdkHeadersBuilder = com.expediagroup.sdk.core2.http.Headers.Builder +typealias SdkRequest = com.expediagroup.sdk.core.http.Request +typealias SdkRequestBody = com.expediagroup.sdk.core.http.RequestBody +typealias SdkResponse = com.expediagroup.sdk.core.http.Response +typealias SdkResponseBuilder = com.expediagroup.sdk.core.http.Response.Builder +typealias SdkHeaders = com.expediagroup.sdk.core.http.Headers +typealias SdkHeadersBuilder = com.expediagroup.sdk.core.http.Headers.Builder class OkHttpTransport( private val okHttpClient: OkHttpClient diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt deleted file mode 100644 index d50b7f43..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/Request.kt +++ /dev/null @@ -1,246 +0,0 @@ -package com.expediagroup.sdk.core.request - -import com.expediagroup.sdk.core.jackson.deserialize -import com.expediagroup.sdk.core.model.Operation -import com.fasterxml.jackson.core.type.TypeReference -import com.google.api.client.googleapis.services.AbstractGoogleClient -import com.google.api.client.googleapis.services.AbstractGoogleClientRequest -import com.google.api.client.http.HttpContent -import com.google.api.client.http.HttpHeaders -import com.google.api.client.http.HttpResponse -import com.google.api.client.http.InputStreamContent -import okio.Buffer -import java.io.InputStream -import java.io.OutputStream -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executor - -/** - * Represents a request to be executed by the Google client. - * - * @param ResponseType The type of response expected from the execution of this request. - * @param client The Google client to be used to execute the request. - * @param method The HTTP method to be used for the request (e.g., GET, POST). - * @param url The URL to which the request will be sent. - * @param content The content to be sent with the request. - * @param headers The HTTP headers to be included in the request, defaults to empty headers. - * @param queryParams The query parameters to be included in the request, defaults to empty map. - * @param responseType The class of the response type. - * @param disableGzipContent Flag to enable or disable GZip content compression, defaults to true. - */ -class Request( - private val client: AbstractGoogleClient, - method: String, - url: String, - content: HttpContent?, - headers: HttpHeaders? = HttpHeaders(), - queryParams: Map>? = emptyMap(), - responseType: Class, - disableGzipContent: Boolean = true -) : AbstractGoogleClientRequest( - client, - method, - url, - content, - responseType -) { - /** - * Constructs a new instance of the Request class using an AbstractGoogleClient and an Operation instance. - * - * @param client The Google client to be used for the request. - * @param operation The operation to be executed, encapsulating the HTTP method, URL, content, headers, and query parameters. - * @param responseType The type into which the response will be deserialized. - */ - constructor( - client: AbstractGoogleClient, - operation: Operation<*>, - responseType: Class - ) : this( - client = client, - method = operation.method, - url = operation.url, - content = operation.getHttpContent(), - headers = HttpHeaders().apply { operation.params?.getHeaders()?.let { putAll(it) } }, - queryParams = operation.params?.getQueryParams(), - responseType = responseType - ) - - /** - * Constructs a new instance of the `Request` class using the provided Google API client, - * GraphQL HTTP request, and the expected response type. - * - * @param client The Google API client used for making HTTP requests. - * @param graphQLRequest The GraphQL HTTP request containing method, URL, body, and headers. - * @param responseType The class of the expected response type. - */ - constructor( - client: AbstractGoogleClient, - graphQLRequest: com.apollographql.apollo.api.http.HttpRequest, - responseType: Class - ) : this( - client = client, - method = graphQLRequest.method.toString().uppercase(), - url = graphQLRequest.url, - content = graphQLRequest.body?.let { - val buffer = Buffer() - it.writeTo(buffer) - - return@let InputStreamContent(graphQLRequest.body?.contentType, buffer.inputStream()) - }, - headers = graphQLRequest.headers.let { - return@let HttpHeaders().apply { - it.forEach { (key, value) -> put(key, value) } - } - }, - responseType = responseType - ) - - init { - this.disableGZipContent = disableGzipContent - client.googleClientRequestInitializer.initialize(this) - - headers?.forEach { (key, value) -> - requestHeaders.put(key, value) - } - - queryParams?.forEach { (key, value) -> - put(key, value) - } - } - - /** - * Executes a request and parses the response into the specified type. - * - * @param T The type into which the response should be parsed. - * @param responseTypeReference A reference to the type into which the response should be parsed. - * @return The parsed response as an object of type T. - */ - inline fun executeAndParseAs(responseTypeReference: TypeReference): T { - return deserialize(executeUnparsed().parseAsString(), responseTypeReference) - } - - /** - * Executes a request asynchronously and parses the response into the specified type. - * - * @param T The type into which the response will be parsed. - * @param responseTypeReference A reference to the type into which the response should be parsed. - * @return A CompletableFuture representing pending completion of the parsing operation, - * with the parsed response of the specified type. - */ - inline fun executeAndParseAsAsync(responseTypeReference: TypeReference): CompletableFuture { - return CompletableFuture.supplyAsync({ executeAndParseAs(responseTypeReference) }) - } - - /** - * Executes a request and parses the response into the specified type asynchronously using the provided executor. - * - * @param T The type into which the response should be parsed. - * @param responseTypeReference A reference to the type into which the response should be parsed. - * @param executor The executor to use for asynchronous execution. - * @return A CompletableFuture containing the parsed response as an object of type T. - */ - inline fun executeAndParseAsAsync( - responseTypeReference: TypeReference, - executor: Executor - ): CompletableFuture { - return CompletableFuture.supplyAsync({ executeAndParseAs(responseTypeReference) }, executor) - } - - /** - * Executes a media request and returns the result as an InputStream. - * - * @return An InputStream containing the media data, or null if the request failed. - */ - public override fun executeMediaAsInputStream(): InputStream? { - return super.executeMediaAsInputStream() - } - - /** - * Executes a media request asynchronously and returns the result as an InputStream. - * - * @return A CompletableFuture containing an InputStream with the media data, or null if the request failed. - */ - fun executeMediaAsInputStreamAsync(): CompletableFuture = - CompletableFuture.supplyAsync(::executeMediaAsInputStream) - - /** - * Executes a media request asynchronously using the provided executor and returns the result as an InputStream. - * - * @param executor The executor to use for the asynchronous execution. - * @return A CompletableFuture containing an InputStream with the media data, or null if the request failed. - */ - fun executeMediaAsInputStreamAsync(executor: Executor): CompletableFuture = - CompletableFuture.supplyAsync(::executeMediaAsInputStream, executor) - - /** - * Executes a request asynchronously without parsing the response. - * - * @param executor The executor to use for asynchronous execution. - * @return A CompletableFuture containing the HttpResponse. - */ - fun executeUnparsedAsync(executor: Executor): CompletableFuture = - CompletableFuture.supplyAsync(::executeUnparsed, executor) - - /** - * Executes a request asynchronously without parsing the response. - * - * @return A CompletableFuture containing the HttpResponse. - */ - fun executeUnparsedAsync(): CompletableFuture = - CompletableFuture.supplyAsync(::executeUnparsed) - - /** - * Executes a request and downloads the response to the specified OutputStream asynchronously. - * - * @param outputStream The OutputStream to which the response will be downloaded. Can be null. - * @return A CompletableFuture representing the pending completion of the download operation. - */ - fun executeAndDownloadToAsync(outputStream: OutputStream?) = - CompletableFuture.supplyAsync { super.executeAndDownloadTo(outputStream) } - - /** - * Executes a request and downloads the response to the specified OutputStream asynchronously using the provided executor. - * - * @param outputStream The OutputStream to which the response will be downloaded. Can be null. - * @param executor The executor to use for asynchronous execution. - * @return A CompletableFuture representing the pending completion of the download operation. - */ - fun executeAndDownloadToAsync(outputStream: OutputStream?, executor: Executor) = - CompletableFuture.supplyAsync({ super.executeAndDownloadTo(outputStream) }, executor) - - /** - * Executes a request without parsing the response and returns an `HttpResponse`. - * - * This method overrides the `executeUnparsed` method from a superclass, executing the request and handling the response. - * If the response indicates an error, it throws an `ExpediaGroupServiceException`. - * Any other exceptions encountered during the execution are also caught and rethrown as `ExpediaGroupServiceException`. - * - * @return The unparsed `HttpResponse` from the executed request. - * @throws ExpediaGroupServiceException if the response indicates an error or if any exception occurs during execution. - */ - override fun executeUnparsed(): HttpResponse { - try { - return super.executeUnparsed().also { - throwOnError(it) - } - } catch (e: Exception) { - throw com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException( - message = e.message - ) - } - } - - /** - * Checks the `HttpResponse` for an error status and throws an `ExpediaGroupServiceException` if an error is detected. - * - * @param response The `HttpResponse` to check for errors. - * @throws ExpediaGroupServiceException if the response status code indicates an error. - */ - private fun throwOnError(response: HttpResponse) { - if (!response.isSuccessStatusCode) { - throw com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException( - message = response.statusMessage - ) - } - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt deleted file mode 100644 index 86bfaa32..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/ApiClientRequestInitializer.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.expediagroup.sdk.core.request.initializer - -import com.expediagroup.sdk.core.model.UserAgent -import com.google.api.client.googleapis.services.AbstractGoogleClientRequest -import com.google.api.client.googleapis.services.CommonGoogleClientRequestInitializer - -/** - * Initializes API client requests with default settings. - * - * This class extends `CommonGoogleClientRequestInitializer` and provides - * a mechanism for setting up default initialization parameters for API client requests. - * - * @constructor Creates an instance of `DefaultApiClientRequestInitializer` with a provided builder. - * - * @param builder The builder used to construct the `DefaultApiClientRequestInitializer` instance. - */ -class ApiClientRequestInitializer( - builder: Builder -) : CommonGoogleClientRequestInitializer(builder) { - companion object { - /** - * Provides a default instance of `DefaultApiClientRequestInitializer` using the internal builder. - * - * This method creates a new instance of `Builder` and uses it to construct and return a - * `DefaultApiClientRequestInitializer` object. It serves as the standard way to obtain - * a pre-configured request initializer for API client requests. - * - * @return A default instance of `DefaultApiClientRequestInitializer` configured with the default builder settings. - */ - @JvmStatic - fun default(): ApiClientRequestInitializer { - val builder = Builder() - return ApiClientRequestInitializer(builder) - } - - /** - * A builder class for creating instances of `DefaultApiClientRequestInitializer`. - * - * This class extends `CommonGoogleClientRequestInitializer.Builder` and overrides the `build` method - * to return a `DefaultApiClientRequestInitializer` with the current builder instance as a parameter. - */ - class Builder : CommonGoogleClientRequestInitializer.Builder() { - override fun build(): ApiClientRequestInitializer { - return ApiClientRequestInitializer(this) - } - } - } - - /** - * Initializes the provided Google client request. - * - *@param request The Google client request to be initialized. - */ - override fun initialize(request: AbstractGoogleClientRequest<*>) { - super.initialize(request) - overrideUserAgent(request) - } - - /** - * Overrides the user agent in the provided Google client request with a custom user agent string. - * - * @param request The Google client request whose user agent will be overridden. - */ - private fun overrideUserAgent(request: AbstractGoogleClientRequest<*>) { - val (jdk, _, os) = request.requestHeaders.getFirstHeaderStringValue("x-goog-api-client") - .split(" ") - - request.requestHeaders.userAgent = UserAgent( - jdkVersion = jdk, - operatingSystem = os - ).toString() - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt deleted file mode 100644 index 4e3fa510..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/InitializerFunctions.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.expediagroup.sdk.core.request.initializer - -import com.expediagroup.sdk.core.request.interceptor.HttpRequestLoggingInterceptor -import com.expediagroup.sdk.core.request.interceptor.HttpResponseLoggingInterceptor -import com.google.api.client.http.HttpRequest - -object InitializerFunctions { - val disableInternalLogging = fun (request: HttpRequest) { - request.isCurlLoggingEnabled = false - request.isLoggingEnabled = false - } - - val attachDefaultRequestLoggingInterceptor = fun (request: HttpRequest) { - request.interceptor = HttpRequestLoggingInterceptor() - } - - val attachDefaultResponseLoggingInterceptor = fun (request: HttpRequest) { - request.responseInterceptor = HttpResponseLoggingInterceptor() - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt deleted file mode 100644 index b3780bcf..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/initializer/SdkRequestInitializer.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.expediagroup.sdk.core.request.initializer - -import com.google.api.client.http.HttpRequest -import com.google.api.client.http.HttpRequestInitializer - -class SdkRequestInitializer( - vararg functions: (HttpRequest) -> Unit -) : HttpRequestInitializer { - private val functions: Array<(HttpRequest) -> Unit> = arrayOf(*functions) - - companion object { - fun default(): SdkRequestInitializer = SdkRequestInitializer( - InitializerFunctions.attachDefaultRequestLoggingInterceptor, - InitializerFunctions.attachDefaultResponseLoggingInterceptor, - InitializerFunctions.disableInternalLogging, - ) - } - - override fun initialize(request: HttpRequest) { - functions.forEach { function -> function(request) } - } - - fun add(vararg initializers: HttpRequestInitializer): SdkRequestInitializer = - SdkRequestInitializer(*functions, *initializers.map { it::initialize }.toTypedArray()) - - fun add(vararg initializers: SdkRequestInitializer): SdkRequestInitializer = - SdkRequestInitializer(*functions, *initializers.map { it::initialize }.toTypedArray()) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt deleted file mode 100644 index c601eea7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpRequestLoggingInterceptor.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.expediagroup.sdk.core.request.interceptor - -import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED -import com.expediagroup.sdk.core.logging.ExpediaGroupLogger -import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.core.logging.LogMessageConstant -import com.expediagroup.sdk.core.logging.LogMessageTag -import com.expediagroup.sdk.core.logging.LOGGABLE_CONTENT_TYPES -import com.expediagroup.sdk.core.logging.mask.isMaskedField -import com.google.api.client.http.HttpExecuteInterceptor -import com.google.api.client.http.HttpRequest -import com.google.api.client.http.InputStreamContent -import okio.Buffer -import okio.IOException - -/** - * HttpRequestLoggingInterceptor is an implementation of HttpExecuteInterceptor that logs HTTP request details, - * such as headers and body content, for outgoing requests. - */ -class HttpRequestLoggingInterceptor : HttpExecuteInterceptor { - private val logger: ExpediaGroupLogger = - ExpediaGroupLoggerFactory.getLogger(HttpRequestLoggingInterceptor::class.java) - - /** - * Logs the HTTP request method, URL, and headers. - * Headers that are deemed sensitive by the `isMaskedField` function are masked. - * - * @param request The HTTP request object containing the details to be logged. - */ - private fun logRequestEventAndHeaders(request: HttpRequest) { - StringBuilder().apply { - appendLine("${request.requestMethod} ${request.url.clone().build()}") - appendLine(LogMessageConstant.REQUEST_HEADERS) - - request.headers.forEach { (key, value) -> - val keyCases = listOf( - key, - key.capitalize(), - key.uppercase(), - key.lowercase(), - ) - - appendLine("${key}: ${if (keyCases.any(::isMaskedField)) OMITTED else value}") - } - - logger.info(this.toString(), LogMessageTag.OUTGOING) - } - } - - /** - * Logs the body of an HTTP request if it meets certain criteria. - * - * If the request body is empty, it skips logging the body. - * If the body content type is not supported for logging, it logs a message indicating this. - * Otherwise, it logs the content of the request. - * - * @param request The HTTP request object containing the body to be logged. - * @throws IOException if an I/O error occurs while reading the request body. - */ - @Throws(IOException::class) - private fun logRequestBody(request: HttpRequest) { - StringBuilder().apply { - appendLine(LogMessageConstant.REQUEST_BODY) - - if (request.content.length == 0L) { - return - } - - if (!canLogBody(request)) { - appendLine(LogMessageConstant.BODY_CONTENT_TYPE_NOT_SUPPORTED.format(request.content.type)) - logger.debug(this.toString(), LogMessageTag.OUTGOING) - return - } - - appendLine(readAndResetContent(request)) - - logger.debug(this.toString(), LogMessageTag.OUTGOING) - } - } - - /** - * Determines if the body of an HTTP request can be logged. - * - * @param request The HTTP request to check. - * @return `true` if the body of the HTTP request can be logged, `false` otherwise. - */ - private fun canLogBody(request: HttpRequest): Boolean { - val hasContent = request.content.length != 0L - - val contentType = request.content.type.split(";").firstOrNull() - val isLoggableContentType = contentType in LOGGABLE_CONTENT_TYPES - - return hasContent.and(isLoggableContentType) - } - - /** - * Reads the content of the provided HTTP request's body and returns it as a string. - * Resets the content of the request to allow it to be read again. - * - * @param request The HTTP request whose content will be read and reset. - * @return The content of the HTTP request as a string. - * @throws IOException If an I/O error occurs while reading the request body. - */ - @Throws(IOException::class) - private fun readAndResetContent(request: HttpRequest): String = Buffer().apply { - request.content.writeTo(outputStream()) - request.content = InputStreamContent(request.content.type, clone().inputStream()) - }.readUtf8() - - /** - * Intercepts an HTTP request to log its details. - * - * This method logs both the headers and the body of the HTTP request. - * It first logs the request method, URL, and headers by calling `logRequestEventAndHeaders`. - * Next, it logs the body of the request by calling `logRequestBody`. - * - * @param request The HTTP request object containing the details to be intercepted and logged. - * @throws IOException if an I/O error occurs while reading the request body. - */ - @Throws(IOException::class) - override fun intercept(request: HttpRequest) { - logRequestEventAndHeaders(request) - logRequestBody(request) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt deleted file mode 100644 index 15ffcbe7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/request/interceptor/HttpResponseLoggingInterceptor.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.expediagroup.sdk.core.request.interceptor - -import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED -import com.expediagroup.sdk.core.logging.LogMessageConstant -import com.expediagroup.sdk.core.logging.LOGGABLE_CONTENT_TYPES -import com.expediagroup.sdk.core.logging.LogMessageTag -import com.expediagroup.sdk.core.logging.ExpediaGroupLogger -import com.expediagroup.sdk.core.logging.ExpediaGroupLoggerFactory -import com.expediagroup.sdk.core.logging.mask.isMaskedField -import com.google.api.client.http.HttpResponse -import com.google.api.client.http.HttpResponseInterceptor -import okio.IOException -import okio.buffer -import okio.source - - -/** - * The HttpResponseLoggingInterceptor class is responsible for intercepting HTTP responses and logging - * relevant information such as headers, status, and body content. - */ -class HttpResponseLoggingInterceptor : HttpResponseInterceptor { - private val logger: ExpediaGroupLogger = - ExpediaGroupLoggerFactory.getLogger(HttpResponseLoggingInterceptor::class.java) - - /** - * Logs the HTTP response event and its headers. This function constructs a log message - * containing the HTTP method, URL, status code, and response headers, and then logs it. - * - * @param response The HTTP response object from which the information is extracted. - */ - private fun logResponseEventAndHeaders(response: HttpResponse) { - StringBuilder().apply { - val request = response.request - - val method = request.requestMethod - val url = request.url.clone().build() - val status = "[${response.statusCode} ${response.statusMessage}]" - - appendLine("Response from: $method $url $status") - appendLine(LogMessageConstant.RESPONSE_HEADERS) - - response.headers.forEach { (key, value) -> - val keyCases = listOf( - key, - key.capitalize(), - key.uppercase(), - key.lowercase(), - ) - - appendLine("${key}: ${if (keyCases.any(::isMaskedField)) OMITTED else value}") - } - - logger.info(this.toString(), LogMessageTag.INCOMING) - } - } - - /** - * Logs the body of an HTTP response if it is safe to log based on its content type and length. - * - * @param response The HTTP response object containing the body to be logged. - * @throws IOException If an I/O error occurs during reading the response content. - */ - @Throws(IOException::class) - private fun logResponseBody(response: HttpResponse) { - StringBuilder().apply stringBuilder@ { - if (setOf(null, 0).contains(response.headers.contentLength)) { - logger.info(LogMessageConstant.EMPTY_OR_UNKNOWN_RESPONSE_BODY, LogMessageTag.INCOMING) - return - } - - if (!canLogBody(response)) { - this@stringBuilder.appendLine(LogMessageConstant.BODY_CONTENT_TYPE_NOT_SUPPORTED) - logger.debug(this.toString(), LogMessageTag.INCOMING) - return - } - - val contentLength = response.headers.contentLength - val contentLengthExceedsThreshold = (contentLength > Int.MAX_VALUE.toLong()) - - this@stringBuilder.appendLine(LogMessageConstant.RESPONSE_BODY) - - if (!response.content.markSupported()) { - logger.error(LogMessageConstant.RESPONSE_CONTENT_INPUT_STREAM_DOES_NOT_SUPPORT_MARK, LogMessageTag.INCOMING) - return - } - - response.content.apply stream@ { - this@stream.mark(contentLength.toInt() + 1) - - this@stringBuilder.appendLine(response.content.source().buffer().readUtf8()) - if (contentLengthExceedsThreshold) { - this@stringBuilder.appendLine(LogMessageConstant.RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE) - } - - this@stream.reset() - } - - logger.info(this.toString(), LogMessageTag.INCOMING) - } - } - - /** - * Determines if the body of the HTTP response can be logged based on content length and content type. - * - * @param response The HTTP response to check. - * @return `true` if the response body can be logged, `false` otherwise. - */ - private fun canLogBody(response: HttpResponse): Boolean { - val hasContent = response.headers.contentLength > 0L - val isLoggableContentType = response.headers.contentType in LOGGABLE_CONTENT_TYPES - - return hasContent.and(isLoggableContentType) - } - - /** - * Intercepts the given HTTP response to log its event, headers, and body. - * - * @param response The HTTP response to be intercepted and logged. - * @throws IOException If an I/O error occurs during logging the response content. - */ - @Throws(IOException::class) - override fun interceptResponse(response: HttpResponse) { - logResponseEventAndHeaders(response) - logResponseBody(response) - } -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt deleted file mode 100644 index 389087fc..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/Trait.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.expediagroup.sdk.core.trait - -/** - * Marker interface for defining traits in a class. Traits serve as a means to define common - * behaviors or attributes that can be shared across different implementations. - */ -interface Trait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt deleted file mode 100644 index 1f38c571..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/AuthenticationHandlerTrait.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.expediagroup.sdk.core.trait.authentication - -import com.expediagroup.sdk.core.trait.Trait -import com.expediagroup.sdk.core.trait.configuration.ClientConfiguration -import com.google.api.client.http.HttpTransport - -/** - * Interface representing a trait for handling authentication. - * - * Implementing classes are responsible for creating an instance - * of the `RefreshAccessTokenTrait` using given client configuration - * and HTTP transport. - */ -interface AuthenticationHandlerTrait : Trait { - fun createAuthenticationHandler( - config: ClientConfiguration, - transport: HttpTransport, - ): RefreshAccessTokenTrait -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt deleted file mode 100644 index 722da5a5..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/authentication/RefreshAccessTokenTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.authentication - -import com.expediagroup.sdk.core.trait.Trait -import com.google.auth.oauth2.AccessToken - -/** - * Interface representing a trait for refreshing access tokens. - * - * Implementing classes are responsible for providing a method to refresh - * access tokens, which can be used for authentication and authorization - * in client-server communications. - */ -interface RefreshAccessTokenTrait : Trait { - fun refreshAccessToken(): AccessToken? -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt deleted file mode 100644 index 8cd6035b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/BuilderTrait.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.expediagroup.sdk.core.trait.common - -import com.expediagroup.sdk.core.trait.Trait - -/** - * Defines a trait for building instances of a specific type. - * - * @param BuiltType The type of object that this builder will create. - */ -interface BuilderTrait: Trait { - fun build(): BuiltType -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt deleted file mode 100644 index 6fe1e5cd..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/ConfigurationTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.common - -import com.expediagroup.sdk.core.trait.Trait - - -/** - * Interface for defining configuration traits. - * - * Classes implementing this interface signify that they possess configurable attributes - * or behaviors which can be shared or reused across various implementations. - * - * This interface extends the `Trait` interface, indicating that it can be used as a marker - * to define common functionalities or properties in a polymorphic way. - */ -interface ConfigurationTrait: Trait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt deleted file mode 100644 index 4f6f462b..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/common/IdTrait.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core.trait.common - -import com.expediagroup.sdk.core.trait.Trait -import java.util.UUID - -/** - * Interface representing a trait for unique identifier management in a class. - * - * Classes implementing this trait inherit the behavior of generating and handling - * a universally unique identifier (UUID). The `id` property provides a generated UUID - * that uniquely identifies an instance. - */ -interface IdTrait: Trait { - val id: UUID - get() = UUID.randomUUID() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt deleted file mode 100644 index cfb29f41..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthEndpointTrait.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface representing a trait that provides the authentication endpoint. - * - * This trait extends `ClientConfiguration` and includes a method to retrieve - * the authentication endpoint URL. Classes implementing this trait should - * provide the necessary logic to fetch or compute the authentication endpoint. - */ -interface AuthEndpointTrait: ClientConfiguration { - fun getAuthEndpoint(): String -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt deleted file mode 100644 index de152b5f..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/AuthenticationStrategyTrait.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -import com.expediagroup.sdk.core.authentication.strategy.AuthenticationStrategy - -/** - * Interface representing a trait for authentication strategies in client configurations. - * - * This trait extends the `ClientConfiguration` interface, thereby inheriting - * its configuration management features and unique identifier characteristics. - * - * Classes implementing this trait should provide the specific implementation - * for retrieving an `AuthenticationStrategy` which dictates how the client - * will handle authentication. - */ -interface AuthenticationStrategyTrait : ClientConfiguration { - fun getAuthenticationStrategy(): AuthenticationStrategy -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt deleted file mode 100644 index e51b972a..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfiguration.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -import com.expediagroup.sdk.core.trait.common.ConfigurationTrait -import com.expediagroup.sdk.core.trait.common.IdTrait - -/** - * Interface that combines configuration traits with an identifier. - * - * This interface extends from `ConfigurationTrait` and `IdTrait`, thereby inheriting the - * behavior of configuration management and uniquely identifying the configured client-instances - * with a universally unique identifier (UUID). - * - * Implementing classes are expected to manage lower-level client configurations and may - * utilize the inherited `id` property to differentiate between instances. - */ -interface ClientConfiguration: ConfigurationTrait, IdTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt deleted file mode 100644 index 36b275fe..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ClientConfigurationTrait.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -import com.expediagroup.sdk.core.trait.common.ConfigurationTrait - -/** - * Interface representing client-specific configuration traits. This interface inherits - * from `ConfigurationTrait` and is intended to define configurations common to client - * implementations. - */ -interface ClientConfigurationTrait: ConfigurationTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt deleted file mode 100644 index a20698f7..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ConnectionTimeoutTrait.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface for configuration traits that require specifying the connection timeout duration. - * - * Classes implementing this trait should provide a method to retrieve the connection timeout value - * in milliseconds. The connection timeout determines the maximum time to wait while establishing a connection. - * - * This trait extends `ClientConfiguration`, which is responsible for handling general client configuration settings. - */ -interface ConnectionTimeoutTrait: ClientConfiguration { - fun getConnectionTimeout(): Long -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt deleted file mode 100644 index a4fbc75e..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/EndpointTrait.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface that represents an endpoint trait in a client configuration. - * - * This trait is responsible for providing the endpoint URL which - * is essential for making network requests. - */ -interface EndpointTrait: ClientConfiguration { - fun getEndpoint(): String -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt deleted file mode 100644 index d9d751b0..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/FullConfigurationTrait.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface representing a full configuration trait which amalgamates - * various individual configuration traits required for client configuration. - * - * This interface extends the following traits: - * - `KeyTrait`: Provides access to the client's key. - * - `SecretTrait`: Provides access to the client's secret. - * - `EndpointTrait`: Provides access to the endpoint. - * - `AuthEndpointTrait`: Provides access to the authentication endpoint. - * - `MaskedLoggingHeadersTrait`: Provides headers that should be masked in logs. - * - `MaskedLoggingBodyFieldsTrait`: Provides body fields that should be masked in logs. - * - `RequestTimeoutTrait`: Provides the request timeout duration. - * - `SocketTimeoutTrait`: Provides the socket timeout duration. - * - `ConnectionTimeoutTrait`: Provides the connection timeout duration. - * - `AuthenticationStrategyTrait`: Provides the authentication strategy to be used. - * - `MaxConnectionsTotalTrait`: Provides the maximum number of total connections allowed. - * - `MaxConnectionsPerRouteTrait`: Provides the maximum number of connections allowed per route. - */ -interface FullConfigurationTrait : - KeyTrait, - SecretTrait, - EndpointTrait, - AuthEndpointTrait, - MaskedLoggingHeadersTrait, - MaskedLoggingBodyFieldsTrait, - RequestTimeoutTrait, - SocketTimeoutTrait, - ConnectionTimeoutTrait, - AuthenticationStrategyTrait, - MaxConnectionsTotalTrait, - MaxConnectionsPerRouteTrait diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt deleted file mode 100644 index 90adc8b3..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/KeyTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * KeyTrait interface for client configurations that provides access to the client's key. - * - * This interface extends ClientConfiguration, thereby inheriting configuration management and - * unique client-instance identification behavior. Classes implementing this interface are expected - * to provide a method to retrieve the client's key for various operations such as authentication. - * - * Functions: - * - getKey: Retrieves the client's key as a String. - */ -interface KeyTrait: ClientConfiguration { - fun getKey(): String -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt deleted file mode 100644 index 6c31de89..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingBodyFieldsTrait.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Trait for managing the body fields that should be masked in logs. - * - * This trait extends `ClientConfiguration`, adding a specific method that provides - * a set of body field names whose contents should not appear in logs. - * - * The `getMaskedLoggingBodyFields` method is used to define which fields in the body of - * requests and responses should be masked for security and privacy reasons. - */ -interface MaskedLoggingBodyFieldsTrait: ClientConfiguration { - fun getMaskedLoggingBodyFields(): Set -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt deleted file mode 100644 index 97c375ee..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaskedLoggingHeadersTrait.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Trait for managing the headers that should be masked in logs. - * - * This trait extends `ClientConfiguration`, adding a specific method to provide - * a set of HTTP header names whose values should not appear in logs. - * - * The `getMaskedLoggingHeaders` method allows specifying headers that need - * to be masked for security and privacy reasons. - */ -interface MaskedLoggingHeadersTrait: ClientConfiguration { - fun getMaskedLoggingHeaders(): Set -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt deleted file mode 100644 index 530a8f1d..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsPerRouteTrait.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface for configuring the maximum number of connections allowed per route. - * - * This interface extends `ClientConfiguration`, inheriting the trait behavior - * necessary for managing client configurations. Classes implementing this - * interface must provide an implementation for retrieving the maximum number - * of connections allowed per route. - * - * Functions: - * - getMaxConnectionsPerRoute: Retrieves the maximum number of connections allowed per route. - */ -interface MaxConnectionsPerRouteTrait: ClientConfiguration { - fun getMaxConnectionsPerRoute(): Int -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt deleted file mode 100644 index b8faa760..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/MaxConnectionsTotalTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface defining a trait for configuring the maximum number of total connections. - * - * This interface extends `ClientConfiguration`, thereby incorporating configuration management - * and unique client-instance identification features. Implementing classes should provide logic - * to retrieve the maximum number of total connections allowed. - * - * Functions: - * - getMaxConnectionsTotal: Retrieves the maximum number of total connections allowed. - */ -interface MaxConnectionsTotalTrait: ClientConfiguration { - fun getMaxConnectionsTotal(): Int -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt deleted file mode 100644 index 05a53dd1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/RequestTimeoutTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface for managing the request timeout configuration. - * - * This trait extends the ClientConfiguration interface, inheriting properties related - * to overall client configuration management and unique client-instance identification. - * Implementing classes should provide a method to retrieve the request timeout duration in milliseconds. - * - * Functions: - * - getRequestTimeout: Retrieves the request timeout duration. - */ -interface RequestTimeoutTrait: ClientConfiguration { - fun getRequestTimeout(): Long -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt deleted file mode 100644 index 8e406dd1..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SecretTrait.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Interface representing a configuration trait that provides access to a secret. - * - * This interface extends `ClientConfiguration`, inheriting the properties and behaviors of - * configuration management and unique client-instance identification. Classes implementing - * `SecretTrait` are expected to provide a method to retrieve a secret value, often used - * in authentication or other secure operations. - * - * Functions: - * - getSecret: Retrieves the client's secret as a String. - */ -interface SecretTrait: ClientConfiguration { - fun getSecret(): String -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt deleted file mode 100644 index 236fa6e3..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/SocketTimeoutTrait.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -/** - * Trait representing the socket timeout configuration for a client. - * - * This interface extends `ClientConfiguration`, incorporating the behavior of configuration management - * and unique client-instance identification. Classes implementing `SocketTimeoutTrait` are expected - * to provide a method to retrieve the socket timeout duration in milliseconds. - * - * Functions: - * - getSocketTimeout: Retrieves the socket timeout duration in milliseconds. - */ -interface SocketTimeoutTrait: ClientConfiguration { - fun getSocketTimeout(): Long -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt deleted file mode 100644 index cbc2cf56..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/trait/configuration/ToClientConfigurationTrait.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.expediagroup.sdk.core.trait.configuration - -import com.expediagroup.sdk.core.trait.Trait - -/** - * An interface representing a trait that can be converted to a `ClientConfigurationTrait`. - * It provides methods to convert with or without default configurations. - */ -interface ToClientConfigurationTrait: Trait { - fun toClientConfiguration(withDefault: ClientConfigurationTrait): ClientConfigurationTrait - - fun toClientConfiguration(): ClientConfigurationTrait -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt deleted file mode 100644 index e3af6928..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/extension/NullableExtension.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.expediagroup.sdk.core2.extension - -import java.nio.charset.Charset - -inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { - return this ?: throw exceptionProvider() -} - -fun Boolean?.orFalseIfNull(): Boolean = this == true - -fun String?.orNullIfBlank(): String? = this?.takeUnless { it.isBlank() } - -fun Charset?.orUtf8() = this ?: Charsets.UTF_8 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt deleted file mode 100644 index b83b5355..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LogMessageTag.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core2.logging.common - -/** - * Enumeration representing the different tags available for log messages. - * - * @property tag The string representation of the log message tag. - */ -enum class LogMessageTag(val tag: String) { - PROGRESSING("PROGRESSING"), - SUCCESS("SUCCESS"), - ERROR("ERROR"), - INCOMING("INCOMING"), - OUTGOING("OUTGOING"); - - override fun toString(): String = tag -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt deleted file mode 100644 index 90fa0831..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core2/logging/common/LoggableContentTypes.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.expediagroup.sdk.core2.logging.common - -import com.expediagroup.sdk.core2.http.ContentType - -/** - * A list of MIME types representing content types that are deemed loggable. - * This collection is used to determine whether the content of HTTP requests or responses - * can be logged based on their MIME types. - */ -val LOGGABLE_CONTENT_TYPES = buildList { - add(ContentType.APPLICATION_ATOM_XML.mimeType) - add(ContentType.APPLICATION_JSON.mimeType) - add(ContentType.APPLICATION_XML.mimeType) - add(ContentType.APPLICATION_FORM_URLENCODED.mimeType) - add(ContentType.APPLICATION_SOAP_XML.mimeType) - add(ContentType.APPLICATION_XHTML_XML.mimeType) - add(ContentType.TEXT_PLAIN.mimeType) - add(ContentType.TEXT_HTML.mimeType) - add(ContentType.TEXT_XML.mimeType) - add(ContentType.DEFAULT_TEXT.mimeType) -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt index 7b366bdf..19537829 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloAliases.kt @@ -6,6 +6,8 @@ import com.apollographql.apollo.api.Error typealias ApolloHttpRequest = HttpRequest +typealias ApolloHttpRequestBuilder = HttpRequest.Builder + typealias ApolloHttpResponse = HttpResponse typealias ApolloHttpResponseBuilder = HttpResponse.Builder diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt index 90a7862d..cbefae78 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt @@ -6,11 +6,11 @@ import com.apollographql.apollo.exception.ApolloNetworkException import com.apollographql.java.client.ApolloDisposable import com.apollographql.java.client.network.http.HttpCallback import com.apollographql.java.client.network.http.HttpEngine -import com.expediagroup.sdk.core2.client.RequestExecutor -import com.expediagroup.sdk.core2.http.MediaType -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.RequestBody -import com.expediagroup.sdk.core2.http.Response +import com.expediagroup.sdk.core.client.RequestExecutor +import com.expediagroup.sdk.core.http.MediaType +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.RequestBody +import com.expediagroup.sdk.core.http.Response import java.io.IOException import java.util.UUID import java.util.concurrent.ConcurrentHashMap diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt index 3086d60b..4a25dbdc 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt @@ -22,7 +22,7 @@ import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.Query import com.apollographql.java.client.ApolloClient import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException -import com.expediagroup.sdk.core2.client.RequestExecutor +import com.expediagroup.sdk.core.client.RequestExecutor import com.expediagroup.sdk.graphql.model.exception.NoDataException import com.expediagroup.sdk.graphql.model.response.Error import com.expediagroup.sdk.graphql.model.response.RawResponse diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt index c6266846..ccaf093e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt @@ -1,16 +1,16 @@ package com.expediagroup.sdk.lodgingconnectivity.common -import com.expediagroup.sdk.core2.authentication.bearer.BearerAuthenticationInterceptor -import com.expediagroup.sdk.core2.authentication.common.Credentials -import com.expediagroup.sdk.core2.client.RequestExecutor -import com.expediagroup.sdk.core2.client.Transport -import com.expediagroup.sdk.core2.http.Request -import com.expediagroup.sdk.core2.http.Response -import com.expediagroup.sdk.core2.interceptor.Interceptor -import com.expediagroup.sdk.core2.interceptor.InterceptorsChainExecutor -import com.expediagroup.sdk.core2.logging.LoggingInterceptor -import com.expediagroup.sdk.core2.okhttp.BaseOkHttpClient -import com.expediagroup.sdk.core2.okhttp.OkHttpTransport +import com.expediagroup.sdk.core.authentication.bearer.BearerAuthenticationInterceptor +import com.expediagroup.sdk.core.authentication.common.Credentials +import com.expediagroup.sdk.core.client.RequestExecutor +import com.expediagroup.sdk.core.client.Transport +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.interceptor.Interceptor +import com.expediagroup.sdk.core.interceptor.InterceptorsChainExecutor +import com.expediagroup.sdk.core.logging.LoggingInterceptor +import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient +import com.expediagroup.sdk.core.okhttp.OkHttpTransport import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration @@ -23,9 +23,8 @@ internal fun getHttpTransport(configuration: ClientConfiguration): Transport = w class DefaultRequestExecutor( configuration: ClientConfiguration, - apiEndpoint: ApiEndpoint, - private val transport: Transport = getHttpTransport(configuration) -) : RequestExecutor(transport) { + apiEndpoint: ApiEndpoint +) : RequestExecutor(getHttpTransport(configuration)) { override val interceptors: List = listOf( LoggingInterceptor(), diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt new file mode 100644 index 00000000..828bfb49 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt @@ -0,0 +1,48 @@ +package com.expediagroup.sdk.lodgingconnectivity.common + +import com.expediagroup.sdk.core.authentication.bearer.BearerAuthenticationInterceptor +import com.expediagroup.sdk.core.authentication.common.Credentials +import com.expediagroup.sdk.core.client.RequestExecutor +import com.expediagroup.sdk.core.client.Transport +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.interceptor.Interceptor +import com.expediagroup.sdk.core.interceptor.InterceptorsChainExecutor +import com.expediagroup.sdk.core.logging.LoggingInterceptor +import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient +import com.expediagroup.sdk.core.okhttp.OkHttpTransport +import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint +import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration +import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration + +internal fun getHttpClientAdapter(configuration: ClientConfiguration): Transport = when (configuration) { + is CustomClientConfiguration -> configuration.transport + is DefaultClientConfiguration -> OkHttpTransport(BaseOkHttpClient.getConfiguredInstance(configuration.buildOkHttpConfiguration())) +} + +class RequestExecutorImpl( + configuration: ClientConfiguration, + apiEndpoint: ApiEndpoint +) : RequestExecutor(getHttpClientAdapter(configuration)) { + + override val interceptors: List = listOf( + LoggingInterceptor(), + BearerAuthenticationInterceptor( + transport = this.transport, + authUrl = apiEndpoint.authEndpoint, + credentials = Credentials(key = configuration.key, secret = configuration.secret) + ) + ) + + override fun execute(request: Request): Response { + val chainExecutor = InterceptorsChainExecutor( + interceptors = interceptors, + request = request, + transport = this.transport + ) + + return chainExecutor.proceed(request) + } +} + diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt index de1cd6f4..6b13d529 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/configuration/ClientConfiguration.kt @@ -16,8 +16,8 @@ package com.expediagroup.sdk.lodgingconnectivity.configuration -import com.expediagroup.sdk.core2.client.Transport -import com.expediagroup.sdk.core2.okhttp.OkHttpClientConfiguration +import com.expediagroup.sdk.core.client.Transport +import com.expediagroup.sdk.core.okhttp.OkHttpClientConfiguration import okhttp3.ConnectionPool import okhttp3.Interceptor diff --git a/docs/logging.md b/docs/logging.md index 60c13230..4d7ebbf8 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -80,12 +80,12 @@ If you are troubleshooting an issue, you can set the log level to DEBUG to get m Example log messages at the DEBUG level: ```text -17:40:08.490 [DefaultDispatcher-worker-3] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Call start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] -17:40:08.496 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Connect start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] -17:40:08.508 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Connect end for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] -17:40:08.508 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Connection acquired for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] -17:40:08.510 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Sending request headers start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] -17:40:08.510 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core.client.OkHttpEventListener - ExpediaSDK: Sending request headers end for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.490 [DefaultDispatcher-worker-3] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Call start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.496 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Connect start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.508 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Connect end for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.508 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Connection acquired for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.510 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Sending request headers start for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] +17:40:08.510 [OkHttp https://test.ean.com/...] DEBUG com.expediagroup.sdk.core3.client.OkHttpEventListener - ExpediaSDK: Sending request headers end for transaction-id: [aff4c00d-6f79-4690-8f60-dd1aaccbfaee] ``` ## Traceability From 459399d4ba3f99e0db2214a2ba043cdbffb5ba3d Mon Sep 17 00:00:00 2001 From: mdwairi Date: Sun, 1 Dec 2024 17:53:15 +0300 Subject: [PATCH 08/15] chore: improve bearer auth implementation --- .../bearer/BearerAuthenticationInterceptor.kt | 25 ++--- .../bearer/BearerAuthenticationManager.kt | 103 +++++++++++------- .../bearer/BearerTokenStorage.kt | 5 +- .../authentication/bearer/TokenResponse.kt | 38 ++++++- .../common/AuthenticationManager.kt | 7 -- .../common/DefaultRequestExecutor.kt | 9 +- .../common/RequestExecutorImpl.kt | 48 -------- 7 files changed, 118 insertions(+), 117 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt index fc7c8263..01566aeb 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt @@ -16,8 +16,6 @@ package com.expediagroup.sdk.core.authentication.bearer -import com.expediagroup.sdk.core.authentication.common.Credentials -import com.expediagroup.sdk.core.client.Transport import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.interceptor.Interceptor @@ -31,18 +29,12 @@ import java.io.IOException * It manages token expiration and re-authentication automatically. If the token is about to expire, the interceptor * synchronously refreshes it before proceeding with the request. * - * Requests to the `authUrl` (authentication endpoint) are excluded from this behavior to prevent recursive authentication loops. - * - * @param transport The [Transport] used for making authentication requests. - * @param authUrl The URL of the authentication endpoint used to retrieve bearer tokens. - * @param credentials The [Credentials] required for authentication. + * @param authManager The [BearerAuthenticationManager] used for making authentication requests execution and parsing. */ class BearerAuthenticationInterceptor( - transport: Transport, - private val authUrl: String, - credentials: Credentials + private val authManager: BearerAuthenticationManager ) : Interceptor { - private val bearerAuthenticationManager = BearerAuthenticationManager(transport, authUrl, credentials) + private val lock = Any() /** @@ -66,7 +58,7 @@ class BearerAuthenticationInterceptor( ensureValidAuthentication() val authorizedRequest = request.newBuilder() - .addHeader("Authorization", bearerAuthenticationManager.getAuthorizationHeaderValue()) + .addHeader("Authorization", authManager.getAuthorizationHeaderValue()) .build() return chain.proceed(authorizedRequest) @@ -75,7 +67,7 @@ class BearerAuthenticationInterceptor( /** * Checks if the given request is for authentication. */ - private fun isAuthenticationRequest(request: Request): Boolean = request.url.toString() == authUrl + private fun isAuthenticationRequest(request: Request): Boolean = request.url.toString() == authManager.authUrl /** * Ensures there is a valid authentication token available. @@ -83,12 +75,13 @@ class BearerAuthenticationInterceptor( * * @throws ExpediaGroupAuthException If authentication fails */ + @Throws(ExpediaGroupAuthException::class) private fun ensureValidAuthentication() { try { - if (bearerAuthenticationManager.needsAuthentication()) { + if (authManager.isTokenAboutToExpire()) { synchronized(lock) { - if (bearerAuthenticationManager.needsAuthentication()) { - bearerAuthenticationManager.authenticate() + if (authManager.isTokenAboutToExpire()) { + authManager.authenticate() } } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index 771e4344..2f06c0c3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -19,17 +19,13 @@ package com.expediagroup.sdk.core.authentication.bearer import com.expediagroup.sdk.core.authentication.common.AuthenticationManager import com.expediagroup.sdk.core.authentication.common.Credentials import com.expediagroup.sdk.core.client.Transport -import com.expediagroup.sdk.core.extension.getOrThrow import com.expediagroup.sdk.core.http.ContentType import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response -import com.expediagroup.sdk.core.http.Status import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException /** * Manages bearer token authentication for HTTP requests. @@ -43,30 +39,52 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule * @param credentials The [Credentials] containing the client key and secret used for authentication. */ class BearerAuthenticationManager( + val authUrl: String, private val transport: Transport, - private val authUrl: String, private val credentials: Credentials ) : AuthenticationManager { @Volatile private var bearerTokenStorage = BearerTokenStorage.empty - private val objectMapper = ObjectMapper() - .registerKotlinModule() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - + /** + * Initiates authentication to obtain a new bearer token. + * + * This method sends a request to the authentication server, parses the response, and + * stores the token for future use. + * + * @throws ExpediaGroupAuthException If the authentication request fails. + * @throws ExpediaGroupResponseParsingException If the response cannot be parsed. + */ + @Throws(ExpediaGroupAuthException::class, ExpediaGroupResponseParsingException::class) override fun authenticate() { clearAuthentication() - - val request = createAuthenticationRequest() - val response = executeAuthenticationRequest(request) - val tokenResponse = parseAuthenticationResponse(response) - storeToken(tokenResponse) + .let { + buildAuthenticationRequest() + }.let { + executeAuthenticationRequest(it) + }.let { + TokenResponse.parse(it) + }.also { + storeToken(it) + } } - override fun needsAuthentication(): Boolean = bearerTokenStorage.isAboutToExpire() + /** + * Checks if the current bearer token is about to expire and needs renewal. + * + * @return `true` if the token is near expiration, `false` otherwise. + */ + fun isTokenAboutToExpire(): Boolean = run { + bearerTokenStorage.isAboutToExpire() + } - override fun clearAuthentication() { + /** + * Clears the stored authentication token. + * + * This method resets the internal token storage, effectively invalidating the current session. + */ + override fun clearAuthentication() = run { bearerTokenStorage = BearerTokenStorage.empty } @@ -75,41 +93,46 @@ class BearerAuthenticationManager( * * @return The token in the format `Bearer ` for use in HTTP headers. */ - fun getAuthorizationHeaderValue(): String = bearerTokenStorage.getAsAuthorizationHeaderValue() + fun getAuthorizationHeaderValue(): String = run { + bearerTokenStorage.getAuthorizationHeaderValue() + } - private fun createAuthenticationRequest(): Request = + /** + * Creates an HTTP request to fetch a new bearer token from the authentication server. + * + * @return A [Request] object configured with the necessary headers and parameters. + */ + private fun buildAuthenticationRequest(): Request = run { Request.Builder() .url(authUrl) .method("POST", RequestBody.create(mapOf("grant_type" to "client_credentials"))) .header("Authorization", credentials.encodeBasic()) .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.mimeType) .build() - - private fun executeAuthenticationRequest(request: Request): Response { - val response = transport.execute(request) - if (!response.isSuccessful) { - throw ExpediaGroupAuthException(response.code, "Authentication failed") - } - return response } - private fun parseAuthenticationResponse(response: Response): TokenResponse { - val responseBody = response.body.getOrThrow { - ExpediaGroupAuthException(Status.INTERNAL_SERVER_ERROR, "Authentication response body is empty") - } - - val responseString = responseBody.source().use { - it.readString(responseBody.contentType()?.charset ?: Charsets.UTF_8) - } - - return try { - objectMapper.readValue(responseString, TokenResponse::class.java) - } catch (e: Exception) { - throw ExpediaGroupResponseParsingException("Failed to parse authentication response", e) + /** + * Executes the authentication request and validates the response. + * + * @param request The [Request] object to be executed. + * @return The [Response] received from the server. + * @throws ExpediaGroupAuthException If the server responds with an error. + */ + @Throws(ExpediaGroupAuthException::class, ExpediaGroupNetworkException::class) + private fun executeAuthenticationRequest(request: Request): Response = run { + transport.execute(request).apply { + if (!this.isSuccessful) { + throw ExpediaGroupAuthException(this.code, "Authentication failed") + } } } - private fun storeToken(tokenResponse: TokenResponse) { + /** + * Stores the retrieved token in internal storage for subsequent use. + * + * @param tokenResponse The [TokenResponse] containing the token and its expiration time. + */ + private fun storeToken(tokenResponse: TokenResponse) = run { bearerTokenStorage = BearerTokenStorage.create( accessToken = tokenResponse.accessToken, expiresIn = tokenResponse.expiresIn diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt index b16717aa..6887ff3b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerTokenStorage.kt @@ -47,15 +47,16 @@ class BearerTokenStorage private constructor( * * @return `true` if the token is about to expire; `false` otherwise. */ - fun isAboutToExpire(): Boolean = + fun isAboutToExpire(): Boolean = run { Instant.now(clock).isAfter(expiryInstant.minusSeconds(expirationBufferSeconds)) + } /** * Formats the bearer token as an `Authorization` header value. * * @return The token in the format `Bearer `. */ - fun getAsAuthorizationHeaderValue(): String = "Bearer $accessToken" + fun getAuthorizationHeaderValue(): String = "Bearer $accessToken" companion object { private const val DEFAULT_EXPIRATION_BUFFER_SECONDS = 60L diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt index cbf71901..6ba2ea9d 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt @@ -16,8 +16,14 @@ package com.expediagroup.sdk.core.authentication.bearer +import com.expediagroup.sdk.core.extension.getOrThrow +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule /** * Represents the response from an authentication server containing a bearer token and its expiration details. @@ -32,4 +38,34 @@ import com.fasterxml.jackson.annotation.JsonProperty data class TokenResponse( @JsonProperty("access_token") val accessToken: String, @JsonProperty("expires_in") val expiresIn: Long -) +) { + companion object { + private val objectMapper = ObjectMapper() + .registerKotlinModule() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + /** + * Parses the response from the authentication server to extract token details. + * + * @param response The [Response] from the authentication server. + * @return A [TokenResponse] object containing the token and its metadata. + * @throws ExpediaGroupResponseParsingException If the response cannot be parsed. + */ + @Throws(ExpediaGroupResponseParsingException::class) + fun parse(response: Response): TokenResponse { + val responseBody = response.body.getOrThrow { + ExpediaGroupResponseParsingException("Authenticate response body is empty or cannot be parsed") + } + + val responseString = responseBody.source().use { + it.readString(responseBody.contentType()?.charset ?: Charsets.UTF_8) + } + + return try { + objectMapper.readValue(responseString, TokenResponse::class.java) + } catch (e: Exception) { + throw ExpediaGroupResponseParsingException("Failed to parse authentication response", e) + } + } + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt index 13a477f5..69b5cc9b 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/common/AuthenticationManager.kt @@ -45,13 +45,6 @@ interface AuthenticationManager { ) fun authenticate() - /** - * Checks if the current authentication state is valid and not about to expire. - * - * @return true if new authentication is needed, false otherwise - */ - fun needsAuthentication(): Boolean - /** * Clears any stored authentication state. */ diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt index ccaf093e..c1835573 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt @@ -1,6 +1,7 @@ package com.expediagroup.sdk.lodgingconnectivity.common import com.expediagroup.sdk.core.authentication.bearer.BearerAuthenticationInterceptor +import com.expediagroup.sdk.core.authentication.bearer.BearerAuthenticationManager import com.expediagroup.sdk.core.authentication.common.Credentials import com.expediagroup.sdk.core.client.RequestExecutor import com.expediagroup.sdk.core.client.Transport @@ -29,9 +30,11 @@ class DefaultRequestExecutor( override val interceptors: List = listOf( LoggingInterceptor(), BearerAuthenticationInterceptor( - transport = this.transport, - authUrl = apiEndpoint.authEndpoint, - credentials = Credentials(configuration.key, configuration.secret) + BearerAuthenticationManager( + transport = this.transport, + authUrl = apiEndpoint.authEndpoint, + credentials = Credentials(configuration.key, configuration.secret) + ) ) ) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt deleted file mode 100644 index 828bfb49..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/RequestExecutorImpl.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.expediagroup.sdk.lodgingconnectivity.common - -import com.expediagroup.sdk.core.authentication.bearer.BearerAuthenticationInterceptor -import com.expediagroup.sdk.core.authentication.common.Credentials -import com.expediagroup.sdk.core.client.RequestExecutor -import com.expediagroup.sdk.core.client.Transport -import com.expediagroup.sdk.core.http.Request -import com.expediagroup.sdk.core.http.Response -import com.expediagroup.sdk.core.interceptor.Interceptor -import com.expediagroup.sdk.core.interceptor.InterceptorsChainExecutor -import com.expediagroup.sdk.core.logging.LoggingInterceptor -import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient -import com.expediagroup.sdk.core.okhttp.OkHttpTransport -import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint -import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration -import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration - -internal fun getHttpClientAdapter(configuration: ClientConfiguration): Transport = when (configuration) { - is CustomClientConfiguration -> configuration.transport - is DefaultClientConfiguration -> OkHttpTransport(BaseOkHttpClient.getConfiguredInstance(configuration.buildOkHttpConfiguration())) -} - -class RequestExecutorImpl( - configuration: ClientConfiguration, - apiEndpoint: ApiEndpoint -) : RequestExecutor(getHttpClientAdapter(configuration)) { - - override val interceptors: List = listOf( - LoggingInterceptor(), - BearerAuthenticationInterceptor( - transport = this.transport, - authUrl = apiEndpoint.authEndpoint, - credentials = Credentials(key = configuration.key, secret = configuration.secret) - ) - ) - - override fun execute(request: Request): Response { - val chainExecutor = InterceptorsChainExecutor( - interceptors = interceptors, - request = request, - transport = this.transport - ) - - return chainExecutor.proceed(request) - } -} - From 82daf2eda20b962a3a76deceb2e885ba8b43f3c4 Mon Sep 17 00:00:00 2001 From: mdwairi Date: Sun, 1 Dec 2024 18:17:06 +0300 Subject: [PATCH 09/15] chore: minor enhancements and cleanups --- code/build.gradle | 6 +- .../sdk/core/extension/NullableExtension.kt | 4 - .../expediagroup/sdk/core/http/RequestBody.kt | 4 +- .../sdk/core/okhttp/BaseOkHttpClient.kt | 37 +++++++ .../core/okhttp/OkHttpClientConfiguration.kt | 99 +++++++++++++++++++ .../graphql/extension/ApolloErrorExtension.kt | 25 ----- 6 files changed, 140 insertions(+), 35 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt diff --git a/code/build.gradle b/code/build.gradle index 31572d75..fd4cc689 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -29,12 +29,10 @@ dependencies { /* Dokka */ dokkaHtmlPlugin 'org.jetbrains.dokka:versioning-plugin:1.9.20' - /* Apollo Kotlin */ - api 'com.apollographql.apollo:apollo-runtime:4.1.0' + /* Apollo */ + api 'com.apollographql.java:client:0.0.2' implementation 'com.apollographql.adapters:apollo-adapters-core:0.0.4' - /* Apollo Java */ - implementation 'com.apollographql.java:client:0.0.2' /* EG SDK Core */ implementation 'com.ebay.ejmask:ejmask-api:1.0.3' diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt index 20a1f910..759b8985 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/extension/NullableExtension.kt @@ -1,7 +1,5 @@ package com.expediagroup.sdk.core.extension -import java.nio.charset.Charset - inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { return this ?: throw exceptionProvider() } @@ -9,5 +7,3 @@ inline fun T?.getOrThrow(exceptionProvider: () -> Throwable): T { fun Boolean?.orFalseIfNull(): Boolean = this == true fun String?.orNullIfBlank(): String? = this?.takeUnless { it.isBlank() } - -fun Charset?.orUtf8() = this ?: Charsets.UTF_8 diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt index 1a2db35b..c7f3ec0d 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt @@ -61,7 +61,7 @@ abstract class RequestBody { */ fun create( inputStream: InputStream, - mediaType: MediaType?, + mediaType: MediaType? = null, contentLength: Long = -1 ): RequestBody { return object : RequestBody() { @@ -88,7 +88,7 @@ abstract class RequestBody { */ fun create( source: Source, - mediaType: MediaType?, + mediaType: MediaType? = null, contentLength: Long = -1 ): RequestBody { return object : RequestBody() { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt index ca8f1017..40cf81a4 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/BaseOkHttpClient.kt @@ -19,16 +19,53 @@ package com.expediagroup.sdk.core.okhttp import java.time.Duration import okhttp3.OkHttpClient +/** + * A utility object for managing and configuring a singleton instance of an `OkHttpClient`. + * + * The `BaseOkHttpClient` object provides methods to retrieve a singleton instance of + * `OkHttpClient` and to configure a new instance based on the provided `OkHttpClientConfiguration`. + * It ensures thread safety and avoids recreating the client unnecessarily. + * + * ## Usage + * - Use `getInstance()` to retrieve the singleton instance of `OkHttpClient`. + * - Use `getConfiguredInstance(configuration)` to create a configured `OkHttpClient` instance + * with specific settings provided via the `OkHttpClientConfiguration` object. + * + * ## Thread Safety + * This class ensures that the singleton instance is initialized in a thread-safe manner using + * the double-checked locking pattern. + */ internal object BaseOkHttpClient { + /** + * Volatile storage for the singleton `OkHttpClient` instance. + * Ensures visibility and prevents duplicate initialization in a multithreaded environment. + */ @Volatile private var instance: OkHttpClient? = null + /** + * Retrieves the singleton instance of `OkHttpClient`. + * + * This method ensures that the instance is initialized lazily and safely for concurrent access. + * If the instance is not yet initialized, it will create a new instance. + * + * @return The singleton instance of `OkHttpClient`. + */ fun getInstance(): OkHttpClient { return instance ?: synchronized(this) { instance ?: OkHttpClient().also { instance = it } } } + /** + * Creates a new `OkHttpClient` instance configured with the provided settings. + * + * This method uses the singleton instance as a base and applies the settings specified + * in the `OkHttpClientConfiguration` object to create a customized `OkHttpClient`. + * + * @param configuration The `OkHttpClientConfiguration` containing settings for the client. + * @return A new `OkHttpClient` instance configured with the specified settings. + */ fun getConfiguredInstance(configuration: OkHttpClientConfiguration): OkHttpClient = getInstance() .newBuilder() .apply { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt index 8ea26ff2..136b2888 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpClientConfiguration.kt @@ -19,6 +19,39 @@ package com.expediagroup.sdk.core.okhttp import okhttp3.ConnectionPool import okhttp3.Interceptor +/** + * Represents configuration options for an `OkHttpClient` instance. + * + * The `OkHttpClientConfiguration` class encapsulates various settings that can be applied + * to an `OkHttpClient`, including interceptors, timeouts, connection pooling, and retry behavior. + * It provides a nested `Builder` class for constructing instances in a flexible and fluent manner. + * + * ## Configuration Options + * - **Interceptors**: Application-level interceptors for modifying requests or responses. + * - **Network Interceptors**: Network-level interceptors for observing network traffic. + * - **Connection Pool**: Customizes the connection pooling behavior. + * - **Retry on Connection Failure**: Specifies whether the client should retry failed connections. + * - **Timeouts**: Configures call, connect, read, and write timeouts. + * + * ## Example Usage + * ```kotlin + * val configuration = OkHttpClientConfiguration.builder() + * .interceptors(listOf(loggingInterceptor)) + * .callTimeout(30_000) // 30 seconds + * .connectTimeout(10_000) // 10 seconds + * .retryOnConnectionFailure(true) + * .build() + * ``` + * + * @property interceptors A list of application-level interceptors to apply. + * @property networkInterceptors A list of network-level interceptors to apply. + * @property connectionPool The connection pool configuration. + * @property retryOnConnectionFailure Whether to retry on connection failure. + * @property callTimeout The timeout for a complete HTTP call, in milliseconds. + * @property connectTimeout The timeout for establishing a connection, in milliseconds. + * @property readTimeout The timeout for reading data from a connection, in milliseconds. + * @property writeTimeout The timeout for writing data to a connection, in milliseconds. + */ data class OkHttpClientConfiguration( val interceptors: List? = null, val networkInterceptors: List? = null, @@ -29,6 +62,22 @@ data class OkHttpClientConfiguration( val readTimeout: Int? = null, val writeTimeout: Int? = null, ) { + + /** + * A builder class for constructing `OkHttpClientConfiguration` instances. + * + * The `Builder` class provides a fluent API for setting configuration options + * and creating an instance of `OkHttpClientConfiguration`. + * + * ## Example Usage + * ```kotlin + * val configuration = OkHttpClientConfiguration.builder() + * .callTimeout(15_000) + * .readTimeout(20_000) + * .retryOnConnectionFailure(true) + * .build() + * ``` + */ open class Builder { private var interceptors: List? = null private var networkInterceptors: List? = null @@ -39,38 +88,83 @@ data class OkHttpClientConfiguration( private var readTimeout: Int? = null private var writeTimeout: Int? = null + /** + * Sets the application-level interceptors. + * @param interceptors A list of interceptors to apply. + * @return The builder instance. + */ fun interceptors(interceptors: List) = apply { this.interceptors = interceptors } + /** + * Sets the network-level interceptors. + * @param networkInterceptors A list of interceptors to apply. + * @return The builder instance. + */ fun networkInterceptors(networkInterceptors: List) = apply { this.networkInterceptors = networkInterceptors } + /** + * Sets the connection pool configuration. + * @param connectionPool The connection pool to use. + * @return The builder instance. + */ fun connectionPool(connectionPool: ConnectionPool) = apply { this.connectionPool = connectionPool } + /** + * Sets whether to retry on connection failure. + * @param retryOnConnectionFailure `true` to retry on failure, `false` otherwise. + * @return The builder instance. + */ fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean) = apply { this.retryOnConnectionFailure = retryOnConnectionFailure } + /** + * Sets the call timeout. + * @param callTimeout The timeout duration in milliseconds. + * @return The builder instance. + */ fun callTimeout(callTimeout: Int) = apply { this.callTimeout = callTimeout } + /** + * Sets the connection timeout. + * @param connectTimeout The timeout duration in milliseconds. + * @return The builder instance. + */ fun connectTimeout(connectTimeout: Int) = apply { this.connectTimeout = connectTimeout } + /** + * Sets the read timeout. + * @param readTimeout The timeout duration in milliseconds. + * @return The builder instance. + */ fun readTimeout(readTimeout: Int) = apply { this.readTimeout = readTimeout } + /** + * Sets the write timeout. + * @param writeTimeout The timeout duration in milliseconds. + * @return The builder instance. + */ fun writeTimeout(writeTimeout: Int) = apply { this.writeTimeout = writeTimeout } + /** + * Builds and returns an `OkHttpClientConfiguration` instance. + * + * @return A configured instance of `OkHttpClientConfiguration`. + */ fun build(): OkHttpClientConfiguration { return OkHttpClientConfiguration( interceptors, @@ -86,6 +180,11 @@ data class OkHttpClientConfiguration( } companion object { + /** + * Creates a new builder for constructing an `OkHttpClientConfiguration`. + * + * @return A new instance of `Builder`. + */ @JvmStatic fun builder() = Builder() } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt deleted file mode 100644 index 2963f44d..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/extension/ApolloErrorExtension.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.graphql.extension - -import com.apollographql.apollo.api.Error - -fun Error.toSDKError() = - com.expediagroup.sdk.graphql.model.response.Error( - message = this.message, - path = this.path?.map { it.toString() } - ) From ff6942497bf5897bd020fc2a92477a3aaf8d7e76 Mon Sep 17 00:00:00 2001 From: mdwairi Date: Sun, 1 Dec 2024 22:08:21 +0300 Subject: [PATCH 10/15] chore: unify Kotlin plugin configs in the root build.gradle --- apollo-compiler-plugin/build.gradle | 11 +---------- build.gradle | 10 ++++++++++ code/build.gradle | 5 ----- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/apollo-compiler-plugin/build.gradle b/apollo-compiler-plugin/build.gradle index 28f248dc..bb077351 100644 --- a/apollo-compiler-plugin/build.gradle +++ b/apollo-compiler-plugin/build.gradle @@ -1,12 +1,3 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' version '2.0.21' -} - -kotlin { - jvmToolchain(8) -} - dependencies { - // Add apollo-compiler as a dependency - implementation("com.apollographql.apollo:apollo-compiler:4.1.0") + implementation 'com.apollographql.apollo:apollo-compiler:4.1.0' } diff --git a/build.gradle b/build.gradle index 97cd1fdd..0b11f37e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,22 @@ +plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' version '2.0.21' +} + subprojects { /* Shared plugins between all modules */ apply plugin: 'java' + apply plugin: 'org.jetbrains.kotlin.jvm' /* Shared repositories between all modules */ repositories { mavenCentral() } + kotlin { + jvmToolchain(8) + } + java { withSourcesJar() sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/code/build.gradle b/code/build.gradle index fd4cc689..756319e2 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -1,5 +1,4 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '2.0.21' id 'org.jetbrains.dokka' version '1.9.20' id 'com.apollographql.apollo' version '4.1.0' @@ -8,10 +7,6 @@ plugins { id 'signing' } -kotlin { - jvmToolchain(8) -} - jar { manifest { attributes( From 7362ce254276c1c3d99db10ba541e6e1cb21b50f Mon Sep 17 00:00:00 2001 From: mdwairi Date: Mon, 2 Dec 2024 00:40:05 +0300 Subject: [PATCH 11/15] fix: improve logging implementation --- .../bearer/BearerAuthenticationManager.kt | 4 +- .../authentication/bearer/TokenResponse.kt | 2 +- .../expediagroup/sdk/core/http/ContentType.kt | 93 ---------- .../com/expediagroup/sdk/core/http/Headers.kt | 9 +- .../expediagroup/sdk/core/http/MediaType.kt | 168 ++++++++++-------- .../expediagroup/sdk/core/http/RequestBody.kt | 11 +- .../sdk/core/http/ResponseBody.kt | 6 +- .../sdk/core/logging/LoggingInterceptor.kt | 86 +-------- .../sdk/core/logging/common/Constant.kt | 14 +- .../sdk/core/logging/common/LogMessageTag.kt | 16 -- .../logging/common/LoggableContentTypes.kt | 27 +-- .../core/logging/common/LoggerDecorator.kt | 75 -------- .../sdk/core/logging/common/RequestLogger.kt | 46 +++++ .../sdk/core/logging/common/ResponseLogger.kt | 44 +++++ .../sdk/core/okhttp/OkHttpTransport.kt | 2 +- .../sdk/graphql/common/ApolloHttpEngine.kt | 2 +- .../graphql/common/DefaultGraphQLExecutor.kt | 2 +- 17 files changed, 220 insertions(+), 387 deletions(-) delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt delete mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index 2f06c0c3..8d0823b3 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -19,7 +19,7 @@ package com.expediagroup.sdk.core.authentication.bearer import com.expediagroup.sdk.core.authentication.common.AuthenticationManager import com.expediagroup.sdk.core.authentication.common.Credentials import com.expediagroup.sdk.core.client.Transport -import com.expediagroup.sdk.core.http.ContentType +import com.expediagroup.sdk.core.http.MediaType import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response @@ -107,7 +107,7 @@ class BearerAuthenticationManager( .url(authUrl) .method("POST", RequestBody.create(mapOf("grant_type" to "client_credentials"))) .header("Authorization", credentials.encodeBasic()) - .header("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.mimeType) + .header("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString()) .build() } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt index 6ba2ea9d..04f5e786 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/TokenResponse.kt @@ -58,7 +58,7 @@ data class TokenResponse( } val responseString = responseBody.source().use { - it.readString(responseBody.contentType()?.charset ?: Charsets.UTF_8) + it.readString(responseBody.mediaType()?.charset ?: Charsets.UTF_8) } return try { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt deleted file mode 100644 index d42e7b69..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/ContentType.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.core.http - -import com.expediagroup.sdk.core.http.ContentType.entries - - -/** - * Enum representing various content types, with their associated MIME type strings. - * - * This enum provides a predefined set of commonly used MIME types for applications, - * making it easy to manage and reference content types in a consistent manner. - * - * @param mimeType The MIME type string associated with the content type. - */ -enum class ContentType(val mimeType: String) { - /** - * MIME type for Atom feeds. - */ - APPLICATION_ATOM_XML("application/atom+xml"), - - /** - * MIME type for JSON data. - */ - APPLICATION_JSON("application/json"), - - /** - * MIME type for generic XML data. - */ - APPLICATION_XML("application/xml"), - - /** - * MIME type for form data submitted as URL-encoded values. - */ - APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded"), - - /** - * MIME type for SOAP XML messages. - */ - APPLICATION_SOAP_XML("application/soap+xml"), - - /** - * MIME type for XHTML documents. - */ - APPLICATION_XHTML_XML("application/xhtml+xml"), - - /** - * MIME type for plain text. - */ - TEXT_PLAIN("text/plain"), - - /** - * MIME type for HTML documents. - */ - TEXT_HTML("text/html"), - - /** - * MIME type for XML text. - */ - TEXT_XML("text/xml"), - - /** - * Wildcard MIME type for any text-based content. - */ - DEFAULT_TEXT("text/*"); - - companion object { - /** - * Finds a [ContentType] by its MIME type string. - * - * @param mimeType The MIME type string to search for. - * @return The matching [ContentType], or `null` if no match is found. - */ - fun fromMimeType(mimeType: String): ContentType? { - return entries.find { it.mimeType.equals(mimeType, ignoreCase = true) } - } - } -} - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt index cd806413..8136aaa8 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/Headers.kt @@ -63,14 +63,7 @@ class Headers private constructor(private val headersMap: Map + val parameters: Map = emptyMap() ) { /** - * Returns the charset specified in the media type, or null if none. + * The full type of the media type, consisting of [type]/[subtype]. + */ + val fullType: String + get() = "$type/$subtype" + + /** + * Retrieves the character set from the parameters, if present. */ val charset: Charset? - get() { - val charsetName = parameters["charset"] ?: return null - return try { - Charset.forName(charsetName) - } catch (e: UnsupportedCharsetException) { + get() = parameters["charset"]?.let { + try { + Charset.forName(it) + } catch (_: UnsupportedCharsetException) { null } } /** - * Returns a new [MediaType] instance with the given [charset] parameter. + * Checks if this media type includes the given media type. + * + * @param other The media type to compare against. + * @return `true` if this media type includes the given media type, `false` otherwise. + */ + fun includes(other: MediaType): Boolean { + if (this.type == "*") { + return true + } else if (this.type.equals(other.type, ignoreCase = true)) { + if (this.subtype == "*" || this.subtype.equals(other.subtype, ignoreCase = true)) { + return true + } + } + return false + } + + /** + * Returns a copy of this `MediaType` with the given charset parameter. * * @param charset The charset to set. - * @return A new [MediaType] with the specified charset. + * @return A new `MediaType` instance with the specified charset. */ fun withCharset(charset: Charset): MediaType { val newParameters = parameters.toMutableMap() newParameters["charset"] = charset.name() - return MediaType(type, subtype, newParameters.toMap()) + return MediaType(type, subtype, newParameters) } override fun toString(): String { - val params = parameters.entries.joinToString("; ") { (name, value) -> - "$name=${quoteIfNeeded(value)}" + val params = parameters.entries.joinToString(separator = ";") { (key, value) -> + "$key=$value" } - return "$type/$subtype${if (params.isNotEmpty()) "; $params" else ""}" + return if (params.isNotEmpty()) "$type/$subtype;$params" else "$type/$subtype" } - companion object { - private const val TOKEN_REGEX = "[!#$%&'*+\\-.^_`|~A-Za-z0-9]+" - private const val QUOTED_STRING_REGEX = "\"(\\\\[\\s\\S]|[^\\\\\"])*\"" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is MediaType) return false - private val TYPE_SUBTYPE_PATTERN = Pattern.compile( - "($TOKEN_REGEX)/($TOKEN_REGEX)", Pattern.CASE_INSENSITIVE - ) + return type.equals(other.type, ignoreCase = true) && + subtype.equals(other.subtype, ignoreCase = true) && + parameters.entries.all { (key, value) -> + other.parameters[key]?.equals(value, ignoreCase = true) == true + } + } - private val PARAMETER_PATTERN = Pattern.compile( - ";\\s*($TOKEN_REGEX)\\s*=\\s*($TOKEN_REGEX|$QUOTED_STRING_REGEX)", Pattern.CASE_INSENSITIVE - ) + override fun hashCode(): Int { + var result = type.lowercase(Locale.getDefault()).hashCode() + result = 31 * result + subtype.lowercase(Locale.getDefault()).hashCode() + result = 31 * result + parameters.mapKeys { it.key.lowercase(Locale.getDefault()) }.hashCode() + return result + } + companion object { /** * Parses a media type string into a [MediaType] object. * * @param mediaType The media type string to parse. - * @return The parsed [MediaType], or null if parsing fails. + * @return The parsed [MediaType]. + * @throws IllegalArgumentException If the media type cannot be parsed. */ - fun parse(mediaType: String): MediaType? { - require(mediaType.isNotBlank()) { "mediaType must not be blank" } + fun parse(mediaType: String): MediaType { + require(mediaType.isNotBlank()) { "Media type must not be blank" } + + val parts = mediaType.split(";").map { it.trim() } + val typeSubtype = parts[0].split("/") - val typeSubtypeMatcher = TYPE_SUBTYPE_PATTERN.matcher(mediaType) - if (!typeSubtypeMatcher.lookingAt()) { - return null + if (typeSubtype.size != 2) { + throw IllegalArgumentException("Invalid media type format: $mediaType") } - val type = typeSubtypeMatcher.group(1).lowercase(Locale.US) - val subtype = typeSubtypeMatcher.group(2).lowercase(Locale.US) - val parameters = mutableMapOf() + val type = typeSubtype[0].lowercase(Locale.getDefault()) + val subtype = typeSubtype[1].lowercase(Locale.getDefault()) - val parameterMatcher = PARAMETER_PATTERN.matcher(mediaType) - var s = typeSubtypeMatcher.end() - while (s < mediaType.length) { - parameterMatcher.region(s, mediaType.length) - if (!parameterMatcher.lookingAt()) { - return null - } - - val name = parameterMatcher.group(1).lowercase(Locale.US) - val valueToken = parameterMatcher.group(2) - val value = if (valueToken.startsWith("\"")) { - unquote(valueToken) - } else { - valueToken + val parameters = mutableMapOf() + for (i in 1 until parts.size) { + val parameter = parts[i] + val idx = parameter.indexOf('=') + if (idx == -1) { + throw IllegalArgumentException("Invalid parameter in media type: $parameter") } + val name = parameter.substring(0, idx).trim().lowercase(Locale.getDefault()) + val value = parameter.substring(idx + 1).trim().trim('"') parameters[name] = value - s = parameterMatcher.end() } - return MediaType(type, subtype, parameters.toMap()) + return MediaType(type, subtype, parameters) } - private fun unquote(quoted: String): String { - val sb = StringBuilder() - var i = 1 // Skip initial quote - while (i < quoted.length - 1) { // Skip ending quote - val c = quoted[i] - if (c == '\\') { - i++ - if (i < quoted.length - 1) { - sb.append(quoted[i]) - } - } else { - sb.append(c) - } - i++ - } - return sb.toString() - } + /** Common media type constants **/ + val ALL = MediaType("*", "*") + val APPLICATION_JSON = MediaType("application", "json") + val APPLICATION_XML = MediaType("application", "xml") + val TEXT_PLAIN = MediaType("text", "plain") + val TEXT_HTML = MediaType("text", "html") + val APPLICATION_OCTET_STREAM = MediaType("application", "octet-stream") + val MULTIPART_FORM_DATA = MediaType("multipart", "form-data") + val APPLICATION_FORM_URLENCODED = MediaType("application", "x-www-form-urlencoded") - private fun quoteIfNeeded(value: String): String { - return if (TOKEN_REGEX.toRegex().matches(value)) { - value - } else { - "\"${value.replace("\\", "\\\\").replace("\"", "\\\"")}\"" - } + /** + * Parses multiple media types from a comma-separated string. + * + * @param mediaTypes The string containing comma-separated media types. + * @return A list of parsed [MediaType] objects. + */ + fun parseMediaTypes(mediaTypes: String): List { + return mediaTypes.split(",").map { parse(it.trim()) } } } } - diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt index c7f3ec0d..060e3e8e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/RequestBody.kt @@ -34,7 +34,7 @@ abstract class RequestBody { /** * Returns the media type of the request body. */ - abstract fun contentType(): MediaType? + abstract fun mediaType(): MediaType? /** * Returns the number of bytes that will be written to [writeTo], or -1 if unknown. @@ -65,7 +65,7 @@ abstract class RequestBody { contentLength: Long = -1 ): RequestBody { return object : RequestBody() { - override fun contentType(): MediaType? = mediaType + override fun mediaType(): MediaType? = mediaType override fun contentLength(): Long = contentLength @@ -92,7 +92,7 @@ abstract class RequestBody { contentLength: Long = -1 ): RequestBody { return object : RequestBody() { - override fun contentType(): MediaType? = mediaType + override fun mediaType(): MediaType? = mediaType override fun contentLength(): Long = contentLength @@ -118,16 +118,13 @@ abstract class RequestBody { charset: Charset = Charsets.UTF_8 ): RequestBody { - val mediaType = MediaType.parse(ContentType.APPLICATION_FORM_URLENCODED.mimeType)?.withCharset(charset) - ?: throw IllegalStateException("Failed to parse media type") - val encodedForm = formData.map { (key, value) -> "${encode(key, charset)}=${encode(value, charset)}" }.joinToString("&") val contentBytes = encodedForm.toByteArray(charset) - return create(contentBytes.inputStream(), mediaType) + return create(contentBytes.inputStream(), MediaType.APPLICATION_FORM_URLENCODED) } private fun encode(value: String, charset: Charset): String { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/http/ResponseBody.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/http/ResponseBody.kt index cc0bd4b3..97b8b4b6 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/http/ResponseBody.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/http/ResponseBody.kt @@ -33,7 +33,7 @@ abstract class ResponseBody : Closeable { /** * Returns the media type of the response body, or null if unknown. */ - abstract fun contentType(): MediaType? + abstract fun mediaType(): MediaType? /** * Returns the content length, or -1 if unknown. @@ -76,7 +76,7 @@ abstract class ResponseBody : Closeable { return object : ResponseBody() { private val source = inputStream.source().buffer() - override fun contentType(): MediaType? = mediaType + override fun mediaType(): MediaType? = mediaType override fun contentLength(): Long = contentLength @@ -94,7 +94,7 @@ abstract class ResponseBody : Closeable { */ fun create(source: BufferedSource, mediaType: MediaType? = null, contentLength: Long = -1L): ResponseBody { return object : ResponseBody() { - override fun contentType(): MediaType? = mediaType + override fun mediaType(): MediaType? = mediaType override fun contentLength(): Long = contentLength diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 3fe4e641..070b6e99 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -1,14 +1,11 @@ package com.expediagroup.sdk.core.logging -import com.expediagroup.sdk.core.http.Request -import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.interceptor.Interceptor -import com.expediagroup.sdk.core.logging.common.LoggerDecorator +import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE +import com.expediagroup.sdk.core.logging.common.RequestLogger +import com.expediagroup.sdk.core.logging.common.ResponseLogger import java.io.IOException -import java.nio.charset.Charset -import okio.Buffer -import okio.BufferedSource import org.slf4j.LoggerFactory /** @@ -19,85 +16,18 @@ import org.slf4j.LoggerFactory class LoggingInterceptor( private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) : Interceptor { - private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java)) + private val logger = LoggerFactory.getLogger(this::class.simpleName) @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - val startTime = System.currentTimeMillis() - logRequest(request) - val response = chain.proceed(request) - logResponse(response, System.currentTimeMillis() - startTime) - - return response - } - - private fun logRequest(request: Request) { - try { - buildLogMessage { - append(">>> HTTP ${request.method} ${request.url}\n") - append("\nHeaders:\n${request.headers}") // TODO: Mask sensitive headers + RequestLogger.log(logger, request, maxBodyLogSize) - request.body?.let { - appendBody(">>>", it.peekContent(maxBodyLogSize, it.contentType()?.charset), it.contentLength()) - } - }.also { - logger.info(it) - } - } catch (e: Exception) { - logger.warn("Failed to log request: ${e.message}", e) - } - } - - private fun logResponse(response: Response, durationMs: Long) { - try { - buildLogMessage { - append("<<< HTTP ${response.code} (${durationMs}ms) ${response.request.url}\n") - append("\nHeaders:\n${response.headers}") // TODO: Mask sensitive headers - - response.body?.let { - appendBody( - "<<<", - it.source().peekContent(maxBodyLogSize, it.contentType()?.charset), - it.contentLength() - ) - } - }.also { - logger.info(it) - } - } catch (e: Exception) { - logger.warn("Failed to log response: ${e.message}", e) - } - } - - private inline fun buildLogMessage(block: StringBuilder.() -> Unit): String = - StringBuilder().apply(block).toString() - - private fun StringBuilder.appendBody(prefix: String, content: String, contentLength: Long) { - if (content.isNotEmpty()) { - append("\n$prefix Body: $content") - if (contentLength > maxBodyLogSize) { - append("\n$prefix Body truncated, showing $maxBodyLogSize/$contentLength bytes") - } - } - } - - private fun RequestBody.peekContent(maxSize: Long, charset: Charset?): String { - val buffer = Buffer() - writeTo(buffer) - val bytesToRead = minOf(maxSize, buffer.size) - return buffer.copy().readString(bytesToRead, charset ?: Charsets.UTF_8) - } + val response = chain.proceed(request) - private fun BufferedSource.peekContent(maxSize: Long, charset: Charset?): String { - val buffer = Buffer() - val bytesToRead = minOf(maxSize, buffer.size) - buffer.write(this.peek(), bytesToRead) - return buffer.copy().readString(charset ?: Charsets.UTF_8) - } + ResponseLogger.log(logger, response, maxBodyLogSize) - companion object { - private const val DEFAULT_MAX_BODY_SIZE = 1024L * 1024L // 1MB + return response } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt index e26fd127..66ad98b9 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt @@ -2,18 +2,6 @@ package com.expediagroup.sdk.core.logging.common object Constant { const val NEWLINE = "\n" - const val COMMA_SPACE = ", " - const val DOUBLE_RIGHT_ANGLE_BRACKETS: String = ">>" - const val REQUEST_HEADERS: String = "Request Headers:" - const val REQUEST_BODY: String = "Request Body:" - const val RESPONSE_HEADERS: String = "Response Headers:" - const val RESPONSE_BODY: String = "Response Body:" - const val EMPTY_OR_UNKNOWN_RESPONSE_BODY: String = "Empty response body or unknown content length" - const val BODY_CONTENT_TYPE_NOT_SUPPORTED = "Content type %s not supported for logging" - const val RESPONSE_TOO_LARGE_TO_BE_LOGGED_WHOLE = "Response too large to be logged whole..." - const val LOGGING_PREFIX = "[ExpediaGroupSDK]" - const val TOKEN_RENEWAL_IN_PROGRESS = "Renewing token" - const val TOKEN_RENEWAL_SUCCESSFUL = "Token renewal successful" - const val TOKEN_RENEWAL_FAILURE = "Token renewal failure" const val OMITTED = "<-- omitted -->" + const val DEFAULT_MAX_BODY_SIZE = 8192L //8KB } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt deleted file mode 100644 index 81f88cfe..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LogMessageTag.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.expediagroup.sdk.core.logging.common - -/** - * Enumeration representing the different tags available for log messages. - * - * @property tag The string representation of the log message tag. - */ -enum class LogMessageTag(val tag: String) { - PROGRESSING("PROGRESSING"), - SUCCESS("SUCCESS"), - ERROR("ERROR"), - INCOMING("INCOMING"), - OUTGOING("OUTGOING"); - - override fun toString(): String = tag -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt index aefef462..b6707b47 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggableContentTypes.kt @@ -1,21 +1,24 @@ package com.expediagroup.sdk.core.logging.common -import com.expediagroup.sdk.core.http.ContentType +import com.expediagroup.sdk.core.http.MediaType /** * A list of MIME types representing content types that are deemed loggable. * This collection is used to determine whether the content of HTTP requests or responses * can be logged based on their MIME types. */ -val LOGGABLE_CONTENT_TYPES = buildList { - add(ContentType.APPLICATION_ATOM_XML.mimeType) - add(ContentType.APPLICATION_JSON.mimeType) - add(ContentType.APPLICATION_XML.mimeType) - add(ContentType.APPLICATION_FORM_URLENCODED.mimeType) - add(ContentType.APPLICATION_SOAP_XML.mimeType) - add(ContentType.APPLICATION_XHTML_XML.mimeType) - add(ContentType.TEXT_PLAIN.mimeType) - add(ContentType.TEXT_HTML.mimeType) - add(ContentType.TEXT_XML.mimeType) - add(ContentType.DEFAULT_TEXT.mimeType) +val LOGGABLE_CONTENT_TYPES = listOf( + MediaType.APPLICATION_JSON, + MediaType.TEXT_PLAIN, + MediaType.TEXT_HTML, + MediaType.APPLICATION_XML, + MediaType.parse("text/*"), // Wildcard to include any text-based types + MediaType.parse("application/*+json"), // Matches custom JSON-based media types like application/.api+json + MediaType.APPLICATION_FORM_URLENCODED +) + +internal fun isLoggable(mediaType: MediaType): Boolean { + return LOGGABLE_CONTENT_TYPES.any { loggableType -> + loggableType.includes(mediaType) + } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt deleted file mode 100644 index b996b3bf..00000000 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.expediagroup.sdk.core.logging.common - -import com.expediagroup.sdk.core.logging.masking.maskLogs -import org.slf4j.Logger - -/** - * The ExpediaGroupLogger class is a decorator for the Logger interface that enhances log messages - * with additional formatting and tagging functionality. - * - * @param logger The underlying Logger instance to delegate logging actions to. - */ -internal class LoggerDecorator(private val logger: Logger) : Logger by logger { - override fun info(msg: String) = logger.info(decorate(msg)) - - fun info(msg: String, vararg tags: LogMessageTag) = logger.info(decorate(msg, tags.toSet())) - - override fun warn(msg: String) = logger.warn(decorate(msg)) - - fun warn(msg: String, vararg tags: LogMessageTag) = logger.warn(decorate(msg, tags.toSet())) - - override fun debug(msg: String) = logger.debug(decorate(msg)) - - fun debug(msg: String, vararg tags: LogMessageTag) = logger.debug(decorate(msg, tags.toSet())) - - override fun error(msg: String) = logger.error(decorate(msg)) - - fun error(msg: String, vararg tags: LogMessageTag) = logger.error(decorate(msg, tags.toSet())) - - /** - * Normalizes a log message by applying specific formatting and tagging. - * - * @param msg The log message to normalize. - * @param tags A set of tags to include in the normalized message. - * @return A formatted and tagged log message. - */ - private fun normalize(msg: String, tags: Set = emptySet()): String = - buildList { - maskLogs(msg) - .trim() - .split(Constant.NEWLINE) - .forEach { line -> - tags.joinToString(Constant.COMMA_SPACE).let { tagsPrefix -> - if (tagsPrefix.isNotBlank()) "[$tagsPrefix]" else "" - }.also { - add("${Constant.DOUBLE_RIGHT_ANGLE_BRACKETS} $it $line".trim()) - } - } - }.joinToString(Constant.NEWLINE) - - /** - * Decorates a log message by prefixing it with a logging prefix and normalizing its format using provided tags. - * - * @param msg The message to be decorated. - * @param tags A set of tags to include in the decorated message. Defaults to an empty set. - * @return The decorated log message with the logging prefix and normalized format. - */ - private fun decorate(msg: String, tags: Set = emptySet()): String = - "${Constant.LOGGING_PREFIX} ${normalize(msg, tags)}".trim() -} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt new file mode 100644 index 00000000..6f74c7a8 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt @@ -0,0 +1,46 @@ +package com.expediagroup.sdk.core.logging.common + +import com.expediagroup.sdk.core.http.Request +import com.expediagroup.sdk.core.http.RequestBody +import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE +import java.io.IOException +import java.nio.charset.Charset +import okio.Buffer +import org.slf4j.Logger + +object RequestLogger { + fun log(logger: Logger, request: Request, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE) { + try { + val requestBodyString = request.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } + + buildString { + append("[timestamp=${System.currentTimeMillis()}] - ") + append("[Outgoing] - ") + append("[Method=${request.method}, URL=${request.url}, Headers=[${request.headers}], Body=[${requestBodyString}]") + }.also { + logger.info(it) + } + + } catch (e: Exception) { + logger.warn("Failed to log request: ${e.message}", e) + } + } + + @Throws(IOException::class) + private fun RequestBody.peekContent(maxSize: Long, charset: Charset?): String { + this.mediaType().also { + if (it === null) { + return "Request body of unknown media type cannot be logged" + } + + if (!isLoggable(it)) { + return "Request body of type ${it.fullType} cannot be logged" + } + } + + val buffer = Buffer() + writeTo(buffer) + val bytesToRead = minOf(maxSize, buffer.size) + return buffer.copy().readString(bytesToRead, charset ?: Charsets.UTF_8) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt new file mode 100644 index 00000000..10140462 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt @@ -0,0 +1,44 @@ +package com.expediagroup.sdk.core.logging.common + +import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.http.ResponseBody +import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE +import java.nio.charset.Charset +import okio.Buffer +import org.slf4j.Logger + +object ResponseLogger { + fun log(logger: Logger, response: Response, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE) { + try { + val responseBodyString = response.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } + + buildString { + append("[timestamp=${System.currentTimeMillis()}] - ") + append("[Incoming] - ") + append("[Code=${response.code}, URL=${response.request.url}, Headers=[${response.headers}], Body=[${responseBodyString}]") + }.also { + logger.info(it) + } + + } catch (e: Exception) { + logger.warn("Failed to log response: ${e.message}", e) + } + } + + private fun ResponseBody.peekContent(maxSize: Long, charset: Charset?): String { + this.mediaType().also { + if (it === null) { + return "Response body of unknown media type cannot be logged" + } + + if (!isLoggable(it)) { + return "Response body of type ${it.fullType} cannot be logged" + } + } + + val buffer = Buffer() + val bytesToRead = minOf(maxSize, this.contentLength()) + buffer.write(this.source().peek(), bytesToRead) + return buffer.copy().readString(charset ?: Charsets.UTF_8) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt index 1195aa08..92694379 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/okhttp/OkHttpTransport.kt @@ -76,7 +76,7 @@ class OkHttpTransport( private fun createRequestBody(body: SdkRequestBody?): OkHttpRequestBody? { return body?.let { object : OkHttpRequestBody() { - override fun contentType() = it.contentType()?.toString()?.toMediaTypeOrNull() + override fun contentType() = it.mediaType()?.toString()?.toMediaTypeOrNull() override fun contentLength() = it.contentLength() @Throws(IOException::class) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt index cbefae78..22345ecd 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/ApolloHttpEngine.kt @@ -87,7 +87,7 @@ class ApolloHttpEngine( request.body?.let { requestBody -> sdkRequestBuilder.method("POST", object : RequestBody() { - override fun contentType(): MediaType? = MediaType.parse(requestBody.contentType) + override fun mediaType(): MediaType? = MediaType.parse(requestBody.contentType) override fun contentLength(): Long = requestBody.contentLength @Throws(IOException::class) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt index 4a25dbdc..23cafb11 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/graphql/common/DefaultGraphQLExecutor.kt @@ -21,8 +21,8 @@ import com.apollographql.apollo.api.Mutation import com.apollographql.apollo.api.Operation import com.apollographql.apollo.api.Query import com.apollographql.java.client.ApolloClient -import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException import com.expediagroup.sdk.core.client.RequestExecutor +import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupServiceException import com.expediagroup.sdk.graphql.model.exception.NoDataException import com.expediagroup.sdk.graphql.model.response.Error import com.expediagroup.sdk.graphql.model.response.RawResponse From 95ddb46db6f38e6a7669f5c938f6176414620df3 Mon Sep 17 00:00:00 2001 From: mdwairi Date: Mon, 2 Dec 2024 01:02:25 +0300 Subject: [PATCH 12/15] fix: log authentication requests --- .../bearer/BearerAuthenticationManager.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index 8d0823b3..764dfb49 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -23,9 +23,12 @@ import com.expediagroup.sdk.core.http.MediaType import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.logging.common.RequestLogger +import com.expediagroup.sdk.core.logging.common.ResponseLogger import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException +import org.slf4j.LoggerFactory /** * Manages bearer token authentication for HTTP requests. @@ -43,6 +46,7 @@ class BearerAuthenticationManager( private val transport: Transport, private val credentials: Credentials ) : AuthenticationManager { + private val logger = LoggerFactory.getLogger(BearerAuthenticationInterceptor::class.simpleName) @Volatile private var bearerTokenStorage = BearerTokenStorage.empty @@ -60,9 +64,13 @@ class BearerAuthenticationManager( override fun authenticate() { clearAuthentication() .let { - buildAuthenticationRequest() + buildAuthenticationRequest().also { + RequestLogger.log(logger, it) + } }.let { - executeAuthenticationRequest(it) + executeAuthenticationRequest(it).also { + ResponseLogger.log(logger, it) + } }.let { TokenResponse.parse(it) }.also { From 71662c005ab495c84ce00a04e70309cc5d1625a9 Mon Sep 17 00:00:00 2001 From: mdwairi Date: Mon, 2 Dec 2024 01:10:06 +0300 Subject: [PATCH 13/15] fix: add logback for examples module --- .../com/expediagroup/sdk/core/logging/common/RequestLogger.kt | 1 - .../com/expediagroup/sdk/core/logging/common/ResponseLogger.kt | 1 - examples/build.gradle | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt index 6f74c7a8..298aa848 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt @@ -14,7 +14,6 @@ object RequestLogger { val requestBodyString = request.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { - append("[timestamp=${System.currentTimeMillis()}] - ") append("[Outgoing] - ") append("[Method=${request.method}, URL=${request.url}, Headers=[${request.headers}], Body=[${requestBodyString}]") }.also { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt index 10140462..e1a79e1f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt @@ -13,7 +13,6 @@ object ResponseLogger { val responseBodyString = response.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { - append("[timestamp=${System.currentTimeMillis()}] - ") append("[Incoming] - ") append("[Code=${response.code}, URL=${response.request.url}, Headers=[${response.headers}], Body=[${responseBodyString}]") }.also { diff --git a/examples/build.gradle b/examples/build.gradle index 6f5f3508..3f5cb978 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,5 @@ dependencies { - implementation 'org.slf4j:slf4j-simple:2.0.16' + runtimeOnly 'ch.qos.logback:logback-classic:1.5.12' implementation(project(":code")) } From 4c90863e7ddcd6a3c8d24568c40052a014bdadb6 Mon Sep 17 00:00:00 2001 From: mdwairi Date: Mon, 2 Dec 2024 01:31:01 +0300 Subject: [PATCH 14/15] chore: minor logging improvements --- .../bearer/BearerAuthenticationManager.kt | 7 ++++--- .../sdk/core/logging/LoggingInterceptor.kt | 7 ++++--- .../expediagroup/sdk/core/logging/common/Constant.kt | 1 + .../sdk/core/logging/common/RequestLogger.kt | 12 +++++++++++- .../sdk/core/logging/common/ResponseLogger.kt | 12 +++++++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index 764dfb49..e2be1e64 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -23,6 +23,7 @@ import com.expediagroup.sdk.core.http.MediaType import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response +import com.expediagroup.sdk.core.logging.common.Constant.EXPEDIA_GROUP_SDK import com.expediagroup.sdk.core.logging.common.RequestLogger import com.expediagroup.sdk.core.logging.common.ResponseLogger import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException @@ -46,7 +47,7 @@ class BearerAuthenticationManager( private val transport: Transport, private val credentials: Credentials ) : AuthenticationManager { - private val logger = LoggerFactory.getLogger(BearerAuthenticationInterceptor::class.simpleName) + private val logger = LoggerFactory.getLogger(EXPEDIA_GROUP_SDK) @Volatile private var bearerTokenStorage = BearerTokenStorage.empty @@ -65,11 +66,11 @@ class BearerAuthenticationManager( clearAuthentication() .let { buildAuthenticationRequest().also { - RequestLogger.log(logger, it) + RequestLogger.log(logger, it, tags = listOf("Authentication")) } }.let { executeAuthenticationRequest(it).also { - ResponseLogger.log(logger, it) + ResponseLogger.log(logger, it, tags = listOf("Authentication")) } }.let { TokenResponse.parse(it) diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 070b6e99..2847373e 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -3,6 +3,7 @@ package com.expediagroup.sdk.core.logging import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE +import com.expediagroup.sdk.core.logging.common.Constant.EXPEDIA_GROUP_SDK import com.expediagroup.sdk.core.logging.common.RequestLogger import com.expediagroup.sdk.core.logging.common.ResponseLogger import java.io.IOException @@ -16,17 +17,17 @@ import org.slf4j.LoggerFactory class LoggingInterceptor( private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) : Interceptor { - private val logger = LoggerFactory.getLogger(this::class.simpleName) + private val logger = LoggerFactory.getLogger(EXPEDIA_GROUP_SDK) @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - RequestLogger.log(logger, request, maxBodyLogSize) + RequestLogger.log(logger, request, maxBodyLogSize = maxBodyLogSize) val response = chain.proceed(request) - ResponseLogger.log(logger, response, maxBodyLogSize) + ResponseLogger.log(logger, response, maxBodyLogSize = maxBodyLogSize) return response } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt index 66ad98b9..dfa1f95f 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/Constant.kt @@ -4,4 +4,5 @@ object Constant { const val NEWLINE = "\n" const val OMITTED = "<-- omitted -->" const val DEFAULT_MAX_BODY_SIZE = 8192L //8KB + const val EXPEDIA_GROUP_SDK = "Expedia Group SDK" } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt index 298aa848..0d75ff60 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt @@ -9,11 +9,21 @@ import okio.Buffer import org.slf4j.Logger object RequestLogger { - fun log(logger: Logger, request: Request, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE) { + fun log( + logger: Logger, + request: Request, + tags: List? = null, + maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE + ) { try { val requestBodyString = request.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { + tags?.let { + append("[") + append(it.joinToString(", ")) + append("] - ") + } append("[Outgoing] - ") append("[Method=${request.method}, URL=${request.url}, Headers=[${request.headers}], Body=[${requestBodyString}]") }.also { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt index e1a79e1f..2f558a76 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt @@ -8,11 +8,21 @@ import okio.Buffer import org.slf4j.Logger object ResponseLogger { - fun log(logger: Logger, response: Response, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE) { + fun log( + logger: Logger, + response: Response, + tags: List? = null, + maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE + ) { try { val responseBodyString = response.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { + tags?.let { + append("[") + append(it.joinToString(", ")) + append("] - ") + } append("[Incoming] - ") append("[Code=${response.code}, URL=${response.request.url}, Headers=[${response.headers}], Body=[${responseBodyString}]") }.also { From 88f7a371278ab79d31fb24d5d8a718a6e07b25dc Mon Sep 17 00:00:00 2001 From: mdwairi Date: Mon, 2 Dec 2024 14:23:53 +0300 Subject: [PATCH 15/15] chore: minor logging improvements --- .../bearer/BearerAuthenticationManager.kt | 11 +++--- .../sdk/core/logging/LoggingInterceptor.kt | 5 ++- .../core/logging/common/LoggerDecorator.kt | 35 +++++++++++++++++++ .../sdk/core/logging/common/RequestLogger.kt | 15 +++----- .../sdk/core/logging/common/ResponseLogger.kt | 15 +++----- .../common/DefaultRequestExecutor.kt | 8 ++++- 6 files changed, 59 insertions(+), 30 deletions(-) create mode 100644 code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt index e2be1e64..67fc1ca1 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt @@ -23,7 +23,7 @@ import com.expediagroup.sdk.core.http.MediaType import com.expediagroup.sdk.core.http.Request import com.expediagroup.sdk.core.http.RequestBody import com.expediagroup.sdk.core.http.Response -import com.expediagroup.sdk.core.logging.common.Constant.EXPEDIA_GROUP_SDK +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.logging.common.RequestLogger import com.expediagroup.sdk.core.logging.common.ResponseLogger import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException @@ -47,7 +47,6 @@ class BearerAuthenticationManager( private val transport: Transport, private val credentials: Credentials ) : AuthenticationManager { - private val logger = LoggerFactory.getLogger(EXPEDIA_GROUP_SDK) @Volatile private var bearerTokenStorage = BearerTokenStorage.empty @@ -66,11 +65,11 @@ class BearerAuthenticationManager( clearAuthentication() .let { buildAuthenticationRequest().also { - RequestLogger.log(logger, it, tags = listOf("Authentication")) + RequestLogger.log(logger, it, "Authentication") } }.let { executeAuthenticationRequest(it).also { - ResponseLogger.log(logger, it, tags = listOf("Authentication")) + ResponseLogger.log(logger, it, "Authentication") } }.let { TokenResponse.parse(it) @@ -147,4 +146,8 @@ class BearerAuthenticationManager( expiresIn = tokenResponse.expiresIn ) } + + private companion object { + private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java.enclosingClass)) + } } diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt index 2847373e..7754c771 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/LoggingInterceptor.kt @@ -3,11 +3,10 @@ package com.expediagroup.sdk.core.logging import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE -import com.expediagroup.sdk.core.logging.common.Constant.EXPEDIA_GROUP_SDK +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.logging.common.RequestLogger import com.expediagroup.sdk.core.logging.common.ResponseLogger import java.io.IOException -import org.slf4j.LoggerFactory /** * An interceptor that logs HTTP requests and responses. @@ -15,9 +14,9 @@ import org.slf4j.LoggerFactory * @param maxBodyLogSize The maximum size of the request/response body to log. Defaults to 1MB. */ class LoggingInterceptor( + private val logger: LoggerDecorator, private val maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) : Interceptor { - private val logger = LoggerFactory.getLogger(EXPEDIA_GROUP_SDK) @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt new file mode 100644 index 00000000..e153ae81 --- /dev/null +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/LoggerDecorator.kt @@ -0,0 +1,35 @@ +package com.expediagroup.sdk.core.logging.common + +import org.slf4j.Logger + +class LoggerDecorator(private val logger: Logger) : Logger by logger { + override fun info(msg: String) = logger.info(decorate(msg)) + + override fun warn(msg: String) = logger.warn(decorate(msg)) + + override fun debug(msg: String) = logger.debug(decorate(msg)) + + override fun error(msg: String) = logger.error(decorate(msg)) + + override fun trace(msg: String) = logger.trace(decorate(msg)) + + fun info(msg: String, vararg tags: String) = logger.info(decorate(msg, tags.toSet())) + + fun warn(msg: String, vararg tags: String) = logger.warn(decorate(msg, tags.toSet())) + + fun debug(msg: String, vararg tags: String) = logger.debug(decorate(msg, tags.toSet())) + + fun error(msg: String, vararg tags: String) = logger.error(decorate(msg, tags.toSet())) + + fun trace(msg: String, vararg tags: String) = logger.trace(decorate(msg, tags.toSet())) + + private fun decorate(msg: String, tags: Set? = null): String = buildString { + append("[${Constant.EXPEDIA_GROUP_SDK}] - ") + tags?.let { + append("[") + append(it.joinToString(", ")) + append("] - ") + } + append(msg.trim()) + } +} diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt index 0d75ff60..39fcf1ce 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/RequestLogger.kt @@ -6,28 +6,21 @@ import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE import java.io.IOException import java.nio.charset.Charset import okio.Buffer -import org.slf4j.Logger object RequestLogger { fun log( - logger: Logger, + logger: LoggerDecorator, request: Request, - tags: List? = null, + vararg tags: String, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) { try { val requestBodyString = request.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { - tags?.let { - append("[") - append(it.joinToString(", ")) - append("] - ") - } - append("[Outgoing] - ") - append("[Method=${request.method}, URL=${request.url}, Headers=[${request.headers}], Body=[${requestBodyString}]") + append("[URL=${request.url}, Method=${request.method}, Headers=[${request.headers}], Body=[${requestBodyString}]") }.also { - logger.info(it) + logger.info(it, "Outgoing", *tags) } } catch (e: Exception) { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt index 2f558a76..7a5c69d7 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/core/logging/common/ResponseLogger.kt @@ -5,28 +5,21 @@ import com.expediagroup.sdk.core.http.ResponseBody import com.expediagroup.sdk.core.logging.common.Constant.DEFAULT_MAX_BODY_SIZE import java.nio.charset.Charset import okio.Buffer -import org.slf4j.Logger object ResponseLogger { fun log( - logger: Logger, + logger: LoggerDecorator, response: Response, - tags: List? = null, + vararg tags: String, maxBodyLogSize: Long = DEFAULT_MAX_BODY_SIZE ) { try { val responseBodyString = response.body?.let { it.peekContent(maxBodyLogSize, it.mediaType()?.charset) } buildString { - tags?.let { - append("[") - append(it.joinToString(", ")) - append("] - ") - } - append("[Incoming] - ") - append("[Code=${response.code}, URL=${response.request.url}, Headers=[${response.headers}], Body=[${responseBodyString}]") + append("[URL=${response.request.url}, Code=${response.code}, Headers=[${response.headers}], Body=[${responseBodyString}]") }.also { - logger.info(it) + logger.info(it, "Incoming", *tags) } } catch (e: Exception) { diff --git a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt index c1835573..029845f9 100644 --- a/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt +++ b/code/src/main/kotlin/com/expediagroup/sdk/lodgingconnectivity/common/DefaultRequestExecutor.kt @@ -10,12 +10,14 @@ import com.expediagroup.sdk.core.http.Response import com.expediagroup.sdk.core.interceptor.Interceptor import com.expediagroup.sdk.core.interceptor.InterceptorsChainExecutor import com.expediagroup.sdk.core.logging.LoggingInterceptor +import com.expediagroup.sdk.core.logging.common.LoggerDecorator import com.expediagroup.sdk.core.okhttp.BaseOkHttpClient import com.expediagroup.sdk.core.okhttp.OkHttpTransport import com.expediagroup.sdk.lodgingconnectivity.configuration.ApiEndpoint import com.expediagroup.sdk.lodgingconnectivity.configuration.ClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.CustomClientConfiguration import com.expediagroup.sdk.lodgingconnectivity.configuration.DefaultClientConfiguration +import org.slf4j.LoggerFactory internal fun getHttpTransport(configuration: ClientConfiguration): Transport = when (configuration) { is CustomClientConfiguration -> configuration.transport @@ -28,7 +30,7 @@ class DefaultRequestExecutor( ) : RequestExecutor(getHttpTransport(configuration)) { override val interceptors: List = listOf( - LoggingInterceptor(), + LoggingInterceptor(logger), BearerAuthenticationInterceptor( BearerAuthenticationManager( transport = this.transport, @@ -47,5 +49,9 @@ class DefaultRequestExecutor( return chainExecutor.proceed(request) } + + companion object { + private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java.enclosingClass)) + } }