diff --git a/.github/workflows/generate-and-publish-sdk-sources.yaml b/.github/workflows/generate-and-publish-sdk-sources.yaml
index 430f639fb..94208ea84 100644
--- a/.github/workflows/generate-and-publish-sdk-sources.yaml
+++ b/.github/workflows/generate-and-publish-sdk-sources.yaml
@@ -12,7 +12,7 @@ on:
jobs:
generate-and-publish-sources:
- uses: ExpediaGroup/expediagroup-java-sdk/.github/workflows/selfserve-full-workflow.yaml@v20241013
+ uses: ExpediaGroup/expediagroup-java-sdk/.github/workflows/selfserve-full-workflow.yaml@nanssari/validations
secrets: inherit
with:
name: xap
@@ -20,3 +20,4 @@ jobs:
transformations: "--headers key --operationIdsToTags"
repository: 'ExpediaGroup/xap-java-sdk'
ref: ${{ github.head_ref || github.ref_name }}
+ sdk_repo_ref: 'nanssari/validations'
diff --git a/code/LICENSE-HEADER.txt b/code/LICENSE-HEADER.txt
new file mode 100644
index 000000000..732a2da66
--- /dev/null
+++ b/code/LICENSE-HEADER.txt
@@ -0,0 +1,13 @@
+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.
\ No newline at end of file
diff --git a/code/README.md b/code/README.md
new file mode 100644
index 000000000..ff08fb5a3
--- /dev/null
+++ b/code/README.md
@@ -0,0 +1,14 @@
+# Welcome to the xap-sdk SDK!
+
+## Usage
+```xml
+
+ com.expediagroup
+ xap-sdk
+ 0.0.18-SNAPSHOT
+
+```
+
+## License
+
+This project is licensed under the Apache License v2.0 - see the [LICENSE](LICENSE) for details.
diff --git a/code/pom.xml b/code/pom.xml
new file mode 100644
index 000000000..10fbf53ea
--- /dev/null
+++ b/code/pom.xml
@@ -0,0 +1,874 @@
+
+
+ 4.0.0
+ com.expediagroup
+ xap-sdk
+ 0.0.18-SNAPSHOT
+ EG xap-sdk for Java
+ EG xap-sdk v0.0.18-SNAPSHOT
+ https://github.com/ExpediaGroup/test-sdk
+ 2022
+ jar
+
+
+
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+ Expedia Group Committers
+ Expedia Group
+ https://expediagroup.com
+
+
+
+
+ scm:git:git@github.com:ExpediaGroup/test-sdk.git
+ scm:git:git@github.com:ExpediaGroup/test-sdk.git
+ https://github.com/ExpediaGroup/test-sdk/
+
+
+
+
+ oss-sonatype
+ Sonatype Nexus Release Repository
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+ oss-sonatype
+ Sonatype Nexus Snapshots Repository
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+ 1.8
+ 8
+
+ UTF-8
+ UTF-8
+
+ 3.8.0
+ 1.8.0
+
+ 0.90
+ com.expediagroup.sdk
+
+ ${project.version}
+ ${project.artifactId}
+
+
+ 3.13.0
+ 3.8.1
+ 3.5.0
+ 3.4.2
+ 3.3.1
+ 3.6.0
+ 3.3.1
+ 3.5.2
+ 3.6.0
+ 3.4.0
+ 0.8.12
+ 1.9.20
+
+ 1.2.1
+ 4.6
+ 1.6.0
+ 2.0.21
+ 1.9.0
+ 2.3.13
+ 0.26.0
+ 2.0.16
+ 1.7.0
+ 3.2.7
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-bom
+ ${kotlin.version}
+ pom
+ import
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-bom
+ ${kotlinx.coroutines.version}
+ pom
+ import
+
+
+ io.ktor
+ ktor-bom
+ ${ktor.version}
+ pom
+ import
+
+
+ org.jetbrains.kotlinx
+ atomicfu-jvm
+ ${kotlin-atomic.version}
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ 2.18.1
+ pom
+ import
+
+
+ com.squareup.okio
+ okio-jvm
+ 3.9.1
+
+
+ org.jetbrains
+ annotations
+ 26.0.1
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.2.5.Final
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.18.1
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${maven-resources-plugin.version}
+
+ false
+
+ @@
+
+
+
+
+ include-domain-helpers
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/kotlin/com/expediagroup/sdk/domain/xap
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${maven-dependency-plugin.version}
+
+
+ analyze
+
+ analyze
+
+
+ true
+ true
+
+
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+
+ all
+ true
+ true
+ bom
+ true
+
+
+
+
+
+
+
+ ${flatten.maven.plugin.version}
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+ attach-test-jar
+
+ test-jar
+
+
+
+
+ true
+
+
+ true
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ -parameters
+ -XDcompilePolicy=simple
+
+
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ kotlin-java-compile
+ compile
+
+ compile
+
+
+
+ kotlin-java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+ org.codehaus.mojo
+ properties-maven-plugin
+ ${properties.maven.plugin.version}
+
+
+ generate-resources
+
+ write-project-properties
+
+
+ ${project.build.outputDirectory}/sdk.properties
+
+
+
+
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+ ${kotlin.version}
+
+
+ compile-kotlin
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+ ${java.release}
+
+
+
+ test-compile-kotlin
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/kotlin
+
+ ${java.release}
+
+
+
+
+
+ kotlinx-serialization
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ ${build-helper-maven-plugin.version}
+
+
+ add-kotlin-sources
+ generate-sources
+
+ add-source
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+
+ add-kotlin-test-sources
+ generate-sources
+
+ add-test-source
+
+
+
+
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+ get-the-year
+
+ timestamp-property
+
+
+ current.year
+ yyyy
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+
+ shade
+
+
+ true
+ true
+ true
+
+
+
+
+
+ org.hibernate
+ ${shadePrefix}.dependencies.org.hibernate
+
+
+
+
+ org.jetbrains.kotlin:*
+ org.jetbrains.kotlinx:*
+ org.slf4j:slf4j-api
+
+
+
+
+
+
+
+
+ org.jetbrains.dokka
+ dokka-maven-plugin
+
+
+ ${project.basedir}/src/main/kotlin/com/expediagroup/sdk/xap
+ ${project.basedir}/src/main/kotlin/com/expediagroup/sdk/domain/xap
+
+
+
+ org.jetbrains.dokka
+ kotlin-as-java-plugin
+ ${dokka-plugin.version}
+
+
+ org.jetbrains.dokka
+ versioning-plugin
+ ${dokka-plugin.version}
+
+
+
+
+ ${project.version}
+ ${dokka-old-versions.location}
+
+
+ true
+
+
+
+ prepare-package
+
+ dokka
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${maven-enforcer-plugin.version}
+
+
+ enforce-pom-standards
+ validate
+
+ enforce
+
+
+
+
+
+
+
+
+ ${minimum.jdk.version}
+
+
+ ${minimum.maven.version}
+
+
+ true
+ Snapshot dependencies must be resolved before releasing
+
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-plugin.version}
+
+ ${project.build.directory}/jacoco.exec
+ ${project.build.directory}/jacoco
+
+
+
+ instrument-coverage
+ initialize
+
+ prepare-agent
+
+
+
+ coverage-report
+ prepare-package
+
+ report
+
+
+
+ coverage-check
+ verify
+
+ check
+
+
+
+
+ CLASS
+
+ *Test
+
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ ${minimum.code.coverage}
+
+
+ BRANCH
+ COVEREDRATIO
+ ${minimum.code.coverage}
+
+
+
+
+
+
+
+
+
+
+ exec-maven-plugin
+ org.codehaus.mojo
+
+
+ delete-empty-files
+ process-sources
+
+ exec
+
+
+ find
+
+ src
+ -type
+ f
+ -empty
+ -print
+ -delete
+
+
+
+
+
+
+
+ com.github.gantsign.maven
+ ktlint-maven-plugin
+ ${ktlint-plugin.version}
+
+
+ format
+
+ format
+
+
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ ${maven.licence.plugin.version}
+
+
+ ${current.year}
+ Expedia, Inc.
+
+
+
+ ./LICENSE-HEADER.txt
+
+ **/*.kt
+
+
+
+
+
+
+ add-license-header
+ process-sources
+
+ format
+
+
+
+
+
+
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ runtime
+
+
+ org.jetbrains
+ annotations
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+
+
+ io.ktor
+ ktor-serialization-jackson-jvm
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ io.ktor
+ ktor-client-core-jvm
+
+
+ io.ktor
+ ktor-client-okhttp-jvm
+
+
+ io.ktor
+ ktor-client-auth-jvm
+
+
+ io.ktor
+ ktor-client-content-negotiation-jvm
+
+
+ io.ktor
+ ktor-http-jvm
+
+
+ io.ktor
+ ktor-utils-jvm
+
+
+ io.ktor
+ ktor-client-logging-jvm
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-core-jvm
+
+
+ io.ktor
+ ktor-serialization-jvm
+
+
+ io.ktor
+ ktor-client-encoding-jvm
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.jetbrains.kotlinx
+ atomicfu-jvm
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.14.0
+
+
+
+ org.apache.commons
+ commons-text
+ 1.12.0
+
+
+
+ com.ebay.ejmask
+ ejmask-api
+ 1.0.3
+
+
+
+ com.ebay.ejmask
+ ejmask-extensions
+ 1.0.3
+
+
+
+
+
+ release
+
+ true
+ false
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven.gpg.plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ ${maven.nexus-staging.plugin.version}
+ true
+
+ oss-sonatype
+ https://oss.sonatype.org/
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
+ **/jacoco.exec
+
+
+ **/PerformanceTest.java
+ @{argLine}
+
+
+
+
+
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+
+ javadoc
+ ${project.basedir}/target/site/apidocs
+ **/*
+ ${project.build.finalName}
+
+ pack-javadoc
+ package
+
+ jar
+
+
+
+
+
+
+ org.jetbrains.dokka
+ dokka-maven-plugin
+
+
+ ${project.basedir}/src/main/kotlin/com/expediagroup/sdk/xap
+
+
+
+ org.jetbrains.dokka
+ kotlin-as-java-plugin
+ ${dokka-plugin.version}
+
+
+ org.jetbrains.dokka
+ versioning-plugin
+ ${dokka-plugin.version}
+
+
+
+
+ ${project.version}
+ ${dokka-old-versions.location}
+
+
+
+
+
+ prepare-package
+
+ javadocJar
+
+
+
+
+
+
+
+
+
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt
new file mode 100644
index 000000000..c1b903440
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseRapidClient.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.RapidClientConfiguration
+import com.expediagroup.sdk.core.configuration.collector.ConfigurationCollector
+import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider
+import com.expediagroup.sdk.core.configuration.provider.RapidConfigurationProvider
+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 BaseRapidClient(
+ namespace: String,
+ clientConfiguration: RapidClientConfiguration,
+ httpClientEngine: HttpClientEngine = DEFAULT_HTTP_CLIENT_ENGINE
+) : Client(namespace) {
+ private val _configurationProvider: ConfigurationProvider =
+ ConfigurationCollector.create(
+ clientConfiguration.toProvider(),
+ RapidConfigurationProvider
+ )
+ private val _httpClient: HttpClient = buildHttpClient(_configurationProvider, AuthenticationStrategy.AuthenticationType.SIGNATURE, httpClientEngine)
+
+ init {
+ finalize()
+ }
+
+ override val configurationProvider: ConfigurationProvider
+ get() = _configurationProvider
+
+ override val httpClient: HttpClient
+ get() = _httpClient
+
+ /** A [BaseRapidClient] builder. */
+ @Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients.
+ abstract class Builder> : Client.Builder()
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt
new file mode 100644
index 000000000..b3f550c53
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/BaseXapClient.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.XapClientConfiguration
+import com.expediagroup.sdk.core.configuration.collector.ConfigurationCollector
+import com.expediagroup.sdk.core.configuration.provider.ConfigurationProvider
+import com.expediagroup.sdk.core.configuration.provider.XapConfigurationProvider
+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 BaseXapClient(
+ namespace: String,
+ clientConfiguration: XapClientConfiguration,
+ httpClientEngine: HttpClientEngine = DEFAULT_HTTP_CLIENT_ENGINE
+) : Client(namespace) {
+ private val _configurationProvider: ConfigurationProvider =
+ ConfigurationCollector.create(
+ clientConfiguration.toProvider(),
+ XapConfigurationProvider
+ )
+ private val _httpClient: HttpClient = buildHttpClient(_configurationProvider, AuthenticationStrategy.AuthenticationType.BASIC, httpClientEngine)
+
+ init {
+ finalize()
+ }
+
+ override val configurationProvider: ConfigurationProvider
+ get() = _configurationProvider
+
+ override val httpClient: HttpClient
+ get() = _httpClient
+
+ /** A [BaseXapClient] builder. */
+ @Suppress("unused", "UnnecessaryAbstractClass") // This is used by the generated SDK clients.
+ abstract class Builder> : Client.Builder()
+}
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
new file mode 100644
index 000000000..dd8e7e166
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Client.kt
@@ -0,0 +1,302 @@
+/*
+ * 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 com.expediagroup.sdk.core.plugin.serialization.SerializationConfiguration
+import com.expediagroup.sdk.core.plugin.serialization.SerializationPlugin
+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
+import okhttp3.Dispatcher
+
+// Create a Dispatcher with limits
+val dispatcher =
+ Dispatcher().apply {
+ maxRequests = 10000 // Maximum number of concurrent requests
+ maxRequestsPerHost = 1000
+ }
+
+val DEFAULT_HTTP_CLIENT_ENGINE: HttpClientEngine =
+ OkHttp.create {
+ config {
+ eventListener(OkHttpEventListener)
+ dispatcher(dispatcher)
+ }
+ }
+
+/**
+ * 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(SerializationPlugin).with(SerializationConfiguration.from(httpClientConfig))
+ 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))
+ }
+
+ 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
new file mode 100644
index 000000000..b2ed3e342
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ClientHelpers.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.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
new file mode 100644
index 000000000..a0a76bcaf
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/Environment.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.model.Properties
+import com.expediagroup.sdk.core.model.TransactionId
+import io.ktor.client.request.HttpRequestBuilder
+import io.ktor.http.HttpHeaders
+
+interface EnvironmentProvider {
+ fun HttpRequestBuilder.appendHeaders(extraHeaders: Map = mapOf(HeaderKey.TRANSACTION_ID to TransactionId().dequeue().toString()))
+}
+
+class DefaultEnvironmentProvider(
+ namespace: String
+) : EnvironmentProvider {
+ private val properties = Properties.from(javaClass.classLoader.getResource("sdk.properties")!!)
+ 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)"
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ override fun HttpRequestBuilder.appendHeaders(extraHeaders: Map) {
+ with(headers) {
+ append(HttpHeaders.UserAgent, userAgent)
+ append(HeaderKey.X_SDK_TITLE, properties["sdk-title"]!!)
+ extraHeaders.forEach { (key, value) -> append(key, value) }
+ }
+ }
+}
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
new file mode 100644
index 000000000..cf28f895d
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/ExpediaGroupClient.kt
@@ -0,0 +1,70 @@
+/*
+ * 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
new file mode 100644
index 000000000..28101ef39
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/HttpHandler.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.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 =
+ 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
new file mode 100644
index 000000000..25dbd741a
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/client/OkHttpEventListener.kt
@@ -0,0 +1,185 @@
+/*
+ * 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/core/configuration/ClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt
new file mode 100644
index 000000000..d8dfdedd9
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ClientConfiguration.kt
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 000000000..1e249b810
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/Credentials.kt
@@ -0,0 +1,45 @@
+/*
+ * 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/core/configuration/ExpediaGroupClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt
new file mode 100644
index 000000000..e5289d9e9
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/ExpediaGroupClientConfiguration.kt
@@ -0,0 +1,47 @@
+/*
+ * 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/core/configuration/RapidClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt
new file mode 100644
index 000000000..cac3637de
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/RapidClientConfiguration.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.BaseRapidClient
+
+/**
+ * Configuration for the [BaseRapidClient].
+ *
+ * @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.
+ */
+data class RapidClientConfiguration(
+ 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
+) : ClientConfiguration
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/XapClientConfiguration.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/XapClientConfiguration.kt
new file mode 100644
index 000000000..f17f9d778
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/XapClientConfiguration.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.BaseXapClient
+
+/**
+ * Configuration for the [BaseXapClient].
+ *
+ * @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.
+ */
+data class XapClientConfiguration(
+ 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
+) : ClientConfiguration
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
new file mode 100644
index 000000000..a83d40fbe
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationCollector.kt
@@ -0,0 +1,73 @@
+/*
+ * 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
new file mode 100644
index 000000000..3a964739c
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/collector/ConfigurationProviderQueue.kt
@@ -0,0 +1,47 @@
+/*
+ * 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
new file mode 100644
index 000000000..ac7f7ea43
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ConfigurationProvider.kt
@@ -0,0 +1,58 @@
+/*
+ * 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
new file mode 100644
index 000000000..75295aeb2
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/ExpediaGroupConfigurationProvider.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.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
new file mode 100644
index 000000000..bc8e06c60
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RapidConfigurationProvider.kt
@@ -0,0 +1,40 @@
+/*
+ * 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
new file mode 100644
index 000000000..fd72dc58e
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/RuntimeConfigurationProvider.kt
@@ -0,0 +1,45 @@
+/*
+ * 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/configuration/provider/XapConfigurationProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/XapConfigurationProvider.kt
new file mode 100644
index 000000000..20857cce7
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/configuration/provider/XapConfigurationProvider.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 XapConfigurationProvider : ConfigurationProvider {
+ override val name: String = "XAP Configuration Provider"
+ override val endpoint: String = "https://apim.expedia.com"
+ 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/constant/Authentication.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.kt
new file mode 100644
index 000000000..2b22a50e1
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Authentication.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.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"
+}
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
new file mode 100644
index 000000000..0004086b7
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/ConfigurationName.kt
@@ -0,0 +1,40 @@
+/*
+ * 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
new file mode 100644
index 000000000..5f7a9d8c1
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/Constant.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.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
+
+ 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
+}
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
new file mode 100644
index 000000000..332204407
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..425d0f5fc
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.core.constant
+
+internal object HeaderKey {
+ const val PAGINATION_TOTAL_RESULTS = "pagination-total-results"
+
+ const val LINK = "link"
+
+ const val TRANSACTION_ID = "Partner-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
new file mode 100644
index 000000000..3c418bc74
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..a7a71ca1c
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..f66681346
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.core.constant
+
+import io.ktor.http.HttpHeaders
+
+internal data object LogMaskingFields {
+ val DEFAULT_MASKED_HEADER_FIELDS: Set = setOf(HttpHeaders.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/core/constant/LogMaskingRegex.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.kt
new file mode 100644
index 000000000..63d51c0be
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LogMaskingRegex.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.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/LoggerName.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggerName.kt
new file mode 100644
index 000000000..749fcd3fe
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..50d9b3bce
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/LoggingMessage.kt
@@ -0,0 +1,32 @@
+/*
+ * 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_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 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
new file mode 100644
index 000000000..cdf04e37d
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/SignatureValues.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.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/ExceptionMessageProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.kt
new file mode 100644
index 000000000..316ef3906
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/ExceptionMessageProvider.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.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/LogMaskingRegexProvider.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.kt
new file mode 100644
index 000000000..475e6f4c2
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LogMaskingRegexProvider.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.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
new file mode 100644
index 000000000..f9d358ad3
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/constant/provider/LoggingMessageProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 io.ktor.http.HttpStatusCode
+
+internal object LoggingMessageProvider {
+ fun getTokenExpiresInMessage(expiresIn: Int) = "New token expires in $expiresIn seconds"
+
+ fun getResponseUnsuccessfulMessage(
+ httpStatusCode: HttpStatusCode,
+ 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/contract/Contract.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.kt
new file mode 100644
index 000000000..6527759ca
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/contract/Contract.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.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/core/model/Headers.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.kt
new file mode 100644
index 000000000..93bc60783
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Headers.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.core.model
+
+import com.expediagroup.sdk.core.constant.HeaderKey
+import io.ktor.http.Headers
+import io.ktor.http.HeadersBuilder
+
+/** 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/core/model/Nothing.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Nothing.kt
new file mode 100644
index 000000000..bd6863c42
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..baf620810
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Operation.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.core.model
+
+abstract class Operation(
+ val url: String,
+ val method: String,
+ val operationId: String,
+ val requestBody: T?,
+ val params: OperationParams?
+) {
+ var transactionId: TransactionId = TransactionId()
+ private set
+}
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
new file mode 100644
index 000000000..c78314c41
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/OperationParams.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.core.model
+
+import io.ktor.http.Headers
+import io.ktor.http.Parameters
+
+interface OperationParams {
+ fun getHeaders(): Headers
+
+ fun getQueryParams(): Parameters
+
+ 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
new file mode 100644
index 000000000..5c93b9990
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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
new file mode 100644
index 000000000..d5bd9747e
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 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/core/model/TransactionId.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.kt
new file mode 100644
index 000000000..07333147e
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/TransactionId.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.core.model
+
+import java.util.UUID
+
+class TransactionId {
+ private var transactionId: UUID = UUID.randomUUID()
+
+ fun peek(): UUID = transactionId
+
+ fun dequeue(): UUID = transactionId.also { transactionId = UUID.randomUUID() }
+}
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
new file mode 100644
index 000000000..02fcaabd8
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExceptionUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * 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/ExpediaGroupException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/ExpediaGroupException.kt
new file mode 100644
index 000000000..cf3d8b72c
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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/core/model/exception/client/ExpediaGroupClientException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupClientException.kt
new file mode 100644
index 000000000..4ea0b2ed2
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.core.model.exception.client
+
+import com.expediagroup.sdk.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/core/model/exception/client/ExpediaGroupConfigurationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupConfigurationException.kt
new file mode 100644
index 000000000..63fd9ca96
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.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/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.kt
new file mode 100644
index 000000000..6d06ffc87
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/ExpediaGroupInvalidFieldNameException.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.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/client/PropertyConstraintViolationException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/PropertyConstraintViolationException.kt
new file mode 100644
index 000000000..8f05fec81
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/client/PropertyConstraintViolationException.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.core.model.exception.client
+
+/**
+ * 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 = "Some field constraints have been violated",
+ constraintViolations: List
+) : ExpediaGroupClientException("$message ${constraintViolations.joinToString(separator = ",\n\t- ", prefix = "[\n\t- ", postfix = "\n]")}")
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
new file mode 100644
index 000000000..64e901c52
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupApiException.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+import com.expediagroup.sdk.core.constant.provider.LoggingMessageProvider.getTransactionIdMessage
+
+abstract class ExpediaGroupApiException(val statusCode: Int, open val errorObject: Any, transactionId: String?) :
+ ExpediaGroupServiceException("Unsuccessful response code [$statusCode]${getTransactionIdMessage(transactionId)}${stringifyErrorObject(errorObject.toString())}", transactionId = transactionId)
+
+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
new file mode 100644
index 000000000..d3d6ac149
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.core.model.exception.service
+
+import io.ktor.http.HttpStatusCode
+
+/**
+ * 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/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt
new file mode 100644
index 000000000..e25db920b
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceDefaultErrorException.kt
@@ -0,0 +1,18 @@
+/*
+ * 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/exception/service/ExpediaGroupServiceException.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/exception/service/ExpediaGroupServiceException.kt
new file mode 100644
index 000000000..130c1c19f
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/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.core.model.exception.service
+
+import com.expediagroup.sdk.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/core/model/paging/Paginator.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt
new file mode 100644
index 000000000..7fbaa064b
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/Paginator.kt
@@ -0,0 +1,89 @@
+/*
+ * 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? =
+ 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().data
+}
+
+/**
+ * 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
new file mode 100644
index 000000000..19897831d
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/model/paging/ResponseState.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.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 = response
+
+ override fun hasNext(): Boolean = true
+}
+
+internal class LastResponseState : ResponseState {
+ override fun getNextResponse(): Response = throw NoSuchElementException()
+
+ override fun hasNext(): Boolean = 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 =
+ runBlocking {
+ val response = client.performGet(link)
+ val body = parseBody(response)
+ Response(response.status.value, body, response.headers.entries())
+ }
+
+ override fun hasNext(): Boolean = true
+
+ private suspend fun parseBody(response: HttpResponse): T = 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 = 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
new file mode 100644
index 000000000..9a688c6b2
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Hook.kt
@@ -0,0 +1,75 @@
+/*
+ * 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
new file mode 100644
index 000000000..6a81db9d9
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/Plugin.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.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
new file mode 100644
index 000000000..8eafdd383
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/PluginConfiguration.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
new file mode 100644
index 000000000..4e0a69c26
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationConfiguration.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
new file mode 100644
index 000000000..ca6d13a76
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationHookFactory.kt
@@ -0,0 +1,65 @@
+/*
+ * 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 = 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
new file mode 100644
index 000000000..174f9feea
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/AuthenticationPlugin.kt
@@ -0,0 +1,42 @@
+/*
+ * 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
new file mode 100644
index 000000000..6a44a53d4
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/AuthenticationStrategy.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.BASIC
+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) {
+ BASIC -> BasicAuthenticationStrategy(configs)
+ BEARER -> ExpediaGroupAuthenticationStrategy(client, configs)
+ SIGNATURE -> RapidAuthenticationStrategy(configs)
+ }
+ }
+
+ enum class AuthenticationType {
+ BASIC,
+ BEARER,
+ SIGNATURE
+ }
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/BasicAuthenticationStrategy.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/BasicAuthenticationStrategy.kt
new file mode 100644
index 000000000..2d87d76df
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/BasicAuthenticationStrategy.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.core.plugin.authentication.strategy
+
+import com.expediagroup.sdk.core.plugin.authentication.AuthenticationConfiguration
+import io.ktor.client.plugins.auth.Auth
+import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
+import io.ktor.client.plugins.auth.providers.basic
+import io.ktor.client.request.HttpRequestBuilder
+
+internal class BasicAuthenticationStrategy(
+ private val configs: AuthenticationConfiguration
+) : AuthenticationStrategy {
+ override fun loadAuth(auth: Auth) {
+ auth.basic {
+ sendWithoutRequest { true }
+ credentials {
+ BasicAuthCredentials(username = configs.credentials.key, password = configs.credentials.secret)
+ }
+ }
+ }
+
+ override fun isTokenAboutToExpire(): Boolean = false
+
+ override fun renewToken() = Unit
+
+ override fun isIdentityRequest(request: HttpRequestBuilder) = true
+
+ override fun getAuthorizationHeader() = String()
+}
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
new file mode 100644
index 000000000..1404a27bc
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/ExpediaGroupAuthenticationStrategy.kt
@@ -0,0 +1,138 @@
+/*
+ * 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 io.ktor.client.HttpClient
+import io.ktor.client.call.body
+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.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
+
+ 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 { renewTokenResponse.body() }
+ 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(
+ val accessToken: String,
+ val expiresIn: Int
+ )
+}
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
new file mode 100644
index 000000000..0927b7566
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/authentication/strategy/RapidAuthenticationStrategy.kt
@@ -0,0 +1,61 @@
+/*
+ * 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
new file mode 100644
index 000000000..c789bc69f
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingConfiguration.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.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
new file mode 100644
index 000000000..658dd6362
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/encoding/EncodingPlugin.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.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
new file mode 100644
index 000000000..89cdd14c4
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingConfiguration.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.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
new file mode 100644
index 000000000..15d58a136
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/exception/ExceptionHandlingPlugin.kt
@@ -0,0 +1,37 @@
+/*
+ * 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
new file mode 100644
index 000000000..a0d28f3cb
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutConfiguration.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.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
new file mode 100644
index 000000000..0590d8847
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/httptimeout/HttpTimeoutPlugin.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.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/ExpediaGroupJsonFieldFilter.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupJsonFieldFilter.kt
new file mode 100644
index 000000000..9a60c277d
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupJsonFieldFilter.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.ebay.ejmask.core.BaseFilter
+
+internal class ExpediaGroupJsonFieldFilter(maskedFields: Array) : BaseFilter(
+ ExpediaGroupJsonFieldPatternBuilder::class.java,
+ *maskedFields
+)
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupJsonFieldPatternBuilder.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupJsonFieldPatternBuilder.kt
new file mode 100644
index 000000000..63ca9cfdb
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupJsonFieldPatternBuilder.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.core.plugin.logging
+
+import com.ebay.ejmask.extenstion.builder.json.JsonFieldPatternBuilder
+import com.expediagroup.sdk.core.constant.LoggingMessage.OMITTED
+
+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/plugin/logging/ExpediaGroupLogger.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.kt
new file mode 100644
index 000000000..6390f5c7a
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLogger.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.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 {
+ private val mask = LogMasker(getMaskedBodyFieldFilters())
+
+ override fun info(msg: String) {
+ if (logger.isInfoEnabled) {
+ logger.info(decorate(msg))
+ }
+ }
+
+ override fun warn(msg: String) {
+ if (logger.isWarnEnabled) {
+ logger.warn(decorate(msg))
+ }
+ }
+
+ override fun debug(msg: String) {
+ if (logger.isDebugEnabled) {
+ logger.debug(decorate(msg))
+ }
+ }
+
+ private fun decorate(msg: String): String = "$LOGGING_PREFIX ${mask(msg)}"
+
+ private fun getMaskedBodyFields(): Set = client?.getLoggingMaskedFieldsProvider()?.getMaskedBodyFields() ?: LogMaskingFields.DEFAULT_MASKED_BODY_FIELDS
+
+ private fun getMaskedBodyFieldFilters(): Iterable =
+ listOf(
+ ExpediaGroupJsonFieldFilter(getMaskedBodyFields().toTypedArray())
+ )
+}
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
new file mode 100644
index 000000000..e2d2326eb
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ExpediaGroupLoggerFactory.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.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
new file mode 100644
index 000000000..36628610f
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LogMasker.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.ebay.ejmask.core.BaseFilter
+import com.ebay.ejmask.core.EJMask
+import com.ebay.ejmask.core.EJMaskInitializer
+import com.ebay.ejmask.core.util.LoggerUtil
+
+internal class LogMasker(
+ filters: Iterable
+) : (String) -> String {
+ init {
+ LoggerUtil.register { _, _, _ -> /* disable logging */ }
+ filters.forEach { EJMaskInitializer.addFilter(it) }
+ }
+
+ override fun invoke(message: String): String = EJMask.mask(message)
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggableContentTypes.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggableContentTypes.kt
new file mode 100644
index 000000000..9031d78b3
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggableContentTypes.kt
@@ -0,0 +1,32 @@
+/*
+ * 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 io.ktor.http.ContentType
+
+val LoggableContentTypes: List =
+ buildList {
+ add(ContentType.Application.Json)
+ add(ContentType.Application.Xml)
+ add(ContentType.Application.Xml_Dtd)
+ add(ContentType.Application.ProblemXml)
+ add(ContentType.Application.Json)
+ add(ContentType.Application.HalJson)
+ add(ContentType.Application.FormUrlEncoded)
+ add(ContentType.Application.ProblemJson)
+ add(ContentType.Text.Xml)
+ add(ContentType.Text.Plain)
+ }
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
new file mode 100644
index 000000000..cd34324a9
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingConfiguration.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.HEADERS,
+ 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
new file mode 100644
index 000000000..033ff1f6f
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingMaskedFieldsProvider.kt
@@ -0,0 +1,45 @@
+/*
+ * 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
new file mode 100644
index 000000000..758a5df72
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/LoggingPlugin.kt
@@ -0,0 +1,50 @@
+/*
+ * 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
new file mode 100644
index 000000000..5a077a148
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/RequestBodyLogger.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.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.http.contentType
+import io.ktor.util.AttributeKey
+import io.ktor.util.pipeline.PipelineContext
+import io.ktor.utils.io.writer
+import kotlinx.coroutines.coroutineScope
+
+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 {
+ val body =
+ when {
+ context.contentType() in LoggableContentTypes -> context.body
+ else -> return "Body of type ${context.contentType()?.contentType} cannot be logged!"
+ }
+
+ if (body is OutputStreamContent) {
+ return coroutineScope {
+ writer {
+ body.writeTo(channel)
+ }.channel.readRemaining().readText()
+ }
+ }
+ return body.toString()
+ }
+
+ override fun prepare(block: RequestBodyLoggerConfig.() -> Unit): RequestBodyLogger = 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
new file mode 100644
index 000000000..c06f8fdc7
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/logging/ResponseBodyLogger.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.HeaderValue
+import com.expediagroup.sdk.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.http.contentType
+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
+ val byteReadChannel: ByteReadChannel = if (response.contentEncoding().equals(HeaderValue.GZIP)) scope.decode(response.content) else response.content
+
+ when {
+ response.contentType() in LoggableContentTypes ->
+ LoggingMessageProvider.getResponseBodyMessage(
+ byteReadChannel.readRemaining().readText(),
+ response.request.headers.getTransactionId()
+ )
+ else -> LoggingMessageProvider.getResponseBodyMessage("Body of type ${response.contentType()?.contentType} cannot be logged!", response.request.headers.getTransactionId())
+ }.let {
+ plugin.log.debug(it)
+ }
+
+ proceed()
+ }
+ }
+
+ override fun prepare(block: ResponseBodyLoggerConfig.() -> Unit): ResponseBodyLogger = 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
new file mode 100644
index 000000000..c1114a221
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestConfiguration.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
new file mode 100644
index 000000000..745c67718
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/request/DefaultRequestPlugin.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.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/ContentNegotiation.kt b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/ContentNegotiation.kt
new file mode 100644
index 000000000..00d4e672a
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/ContentNegotiation.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.logging.ExpediaGroupLoggerFactory
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.HttpClientPlugin
+import io.ktor.client.plugins.contentnegotiation.ContentConverterException
+import io.ktor.client.plugins.contentnegotiation.JsonContentTypeMatcher
+import io.ktor.client.request.HttpRequestBuilder
+import io.ktor.client.request.HttpRequestPipeline
+import io.ktor.client.statement.HttpResponseContainer
+import io.ktor.client.statement.HttpResponsePipeline
+import io.ktor.client.utils.EmptyContent
+import io.ktor.http.ContentType
+import io.ktor.http.ContentTypeMatcher
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.Url
+import io.ktor.http.charset
+import io.ktor.http.content.NullBody
+import io.ktor.http.content.OutgoingContent
+import io.ktor.http.contentType
+import io.ktor.serialization.Configuration
+import io.ktor.serialization.ContentConverter
+import io.ktor.serialization.deserialize
+import io.ktor.serialization.suitableCharset
+import io.ktor.util.AttributeKey
+import io.ktor.util.InternalAPI
+import io.ktor.util.reflect.TypeInfo
+import io.ktor.utils.io.ByteReadChannel
+import io.ktor.utils.io.charsets.Charset
+import java.io.InputStream
+import kotlin.reflect.KClass
+
+internal val DefaultCommonIgnoredTypes: Set> =
+ setOf(
+ ByteArray::class,
+ String::class,
+ HttpStatusCode::class,
+ ByteReadChannel::class,
+ OutgoingContent::class
+ )
+
+internal val DefaultIgnoredTypes: Set> = mutableSetOf(InputStream::class)
+
+internal class ContentNegotiation(
+ private val registrations: List,
+ private val ignoredTypes: Set>
+) {
+ internal class Config : Configuration {
+ internal class ConverterRegistration(
+ val converter: ContentConverter,
+ val contentTypeToSend: ContentType,
+ val contentTypeMatcher: ContentTypeMatcher
+ )
+
+ val registrations = mutableListOf()
+ val ignoredTypes: MutableSet> =
+ (DefaultIgnoredTypes + DefaultCommonIgnoredTypes).toMutableSet()
+
+ override fun register(
+ contentType: ContentType,
+ converter: T,
+ configuration: T.() -> Unit
+ ) {
+ val matcher =
+ when (contentType) {
+ ContentType.Application.Json -> JsonContentTypeMatcher
+ else -> defaultMatcher(contentType)
+ }
+ register(contentType, converter, matcher, configuration)
+ }
+
+ private fun register(
+ contentTypeToSend: ContentType,
+ converter: T,
+ contentTypeMatcher: ContentTypeMatcher,
+ configuration: T.() -> Unit
+ ) {
+ val registration =
+ ConverterRegistration(
+ converter.apply(configuration),
+ contentTypeToSend,
+ contentTypeMatcher
+ )
+ registrations.add(registration)
+ }
+
+ private fun defaultMatcher(pattern: ContentType): ContentTypeMatcher =
+ object : ContentTypeMatcher {
+ override fun contains(contentType: ContentType): Boolean = contentType.match(pattern)
+ }
+ }
+
+ private val log = ExpediaGroupLoggerFactory.getLogger(this::class.java)
+
+ internal suspend fun convertRequest(
+ request: HttpRequestBuilder,
+ body: Any
+ ): Any? {
+ if (body is OutgoingContent || ignoredTypes.any { it.isInstance(body) }) {
+ log.trace(
+ "Body type ${body::class} is in ignored types. " +
+ "Skipping ContentNegotiation for ${request.url}."
+ )
+ return null
+ }
+ val contentType =
+ request.contentType() ?: run {
+ log.trace("Request doesn't have Content-Type header. Skipping ContentNegotiation for ${request.url}.")
+ return null
+ }
+
+ if (body is Unit) {
+ log.trace("Sending empty body for ${request.url}")
+ request.headers.remove(HttpHeaders.ContentType)
+ return EmptyContent
+ }
+
+ val matchingRegistrations =
+ registrations.filter { it.contentTypeMatcher.contains(contentType) }
+ .takeIf { it.isNotEmpty() } ?: run {
+ log.trace(
+ "None of the registered converters match request Content-Type=$contentType. " +
+ "Skipping ContentNegotiation for ${request.url}."
+ )
+ return null
+ }
+ if (request.bodyType == null) {
+ log.trace("Request has unknown body type. Skipping ContentNegotiation for ${request.url}.")
+ return null
+ }
+ request.headers.remove(HttpHeaders.ContentType)
+
+ // Pick the first one that can convert the subject successfully
+ val serializedContent =
+ matchingRegistrations.firstNotNullOfOrNull { registration ->
+ val result =
+ registration.converter.serializeNullable(
+ contentType,
+ contentType.charset() ?: Charsets.UTF_8,
+ request.bodyType!!,
+ body.takeIf { it != NullBody }
+ )
+ if (result != null) {
+ log.trace("Converted request body using ${registration.converter} for ${request.url}")
+ }
+ result
+ } ?: throw ContentConverterException(
+ "Can't convert $body with contentType $contentType using converters " +
+ matchingRegistrations.joinToString { it.converter.toString() }
+ )
+
+ return serializedContent
+ }
+
+ @OptIn(InternalAPI::class)
+ internal suspend fun convertResponse(
+ requestUrl: Url,
+ info: TypeInfo,
+ body: Any,
+ responseContentType: ContentType,
+ charset: Charset = Charsets.UTF_8
+ ): Any? {
+ if (body !is ByteReadChannel) {
+ log.trace("Response body is already transformed. Skipping ContentNegotiation for $requestUrl.")
+ return null
+ }
+ if (info.type in ignoredTypes) {
+ log.trace(
+ "Response body type ${info.type} is in ignored types. " +
+ "Skipping ContentNegotiation for $requestUrl."
+ )
+ return null
+ }
+
+ log.debug("Test: ${registrations.size}")
+ val suitableConverters =
+ registrations
+ .filter { it.contentTypeMatcher.contains(responseContentType) }
+ .map { it.converter }
+ .takeIf { it.isNotEmpty() }
+ ?: run {
+ log.trace(
+ "None of the registered converters match response with Content-Type=$responseContentType. " +
+ "Skipping ContentNegotiation for $requestUrl."
+ )
+ return null
+ }
+
+ val result = suitableConverters.deserialize(body, info, charset)
+ if (result !is ByteReadChannel) {
+ log.trace("Response body was converted to ${result::class} for $requestUrl.")
+ }
+ return result
+ }
+
+ companion object Plugin : HttpClientPlugin {
+ override val key: AttributeKey = AttributeKey("ContentNegotiation")
+ private val log = ExpediaGroupLoggerFactory.getLogger(this::class.java)
+
+ override fun install(
+ plugin: ContentNegotiation,
+ scope: HttpClient
+ ) {
+ scope.requestPipeline.intercept(HttpRequestPipeline.Transform) {
+ val result = plugin.convertRequest(context, subject) ?: return@intercept
+ proceedWith(result)
+ }
+
+ scope.responsePipeline.intercept(HttpResponsePipeline.Transform) { (info, body) ->
+ val contentType =
+ context.response.contentType() ?: run {
+ log.trace("Response doesn't have \"Content-Type\" header, skipping ContentNegotiation plugin")
+ return@intercept
+ }
+ val charset = context.request.headers.suitableCharset()
+
+ val deserializedBody =
+ plugin.convertResponse(context.request.url, info, body, contentType, charset)
+ ?: return@intercept
+ val result = HttpResponseContainer(info, deserializedBody)
+ proceedWith(result)
+ }
+ }
+
+ override fun prepare(block: Config.() -> Unit): ContentNegotiation {
+ val config = Config().apply(block)
+ return ContentNegotiation(config.registrations, config.ignoredTypes)
+ }
+ }
+}
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
new file mode 100644
index 000000000..dc1546df3
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationConfiguration.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.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
new file mode 100644
index 000000000..eda9abc14
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/core/plugin/serialization/SerializationPlugin.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.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/xap/client/XapClient.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/client/XapClient.kt
new file mode 100644
index 000000000..00e64f468
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/client/XapClient.kt
@@ -0,0 +1,303 @@
+/*
+ * 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.xap.client
+
+import com.expediagroup.sdk.core.client.BaseXapClient
+import com.expediagroup.sdk.core.configuration.XapClientConfiguration
+import com.expediagroup.sdk.core.model.EmptyResponse
+import com.expediagroup.sdk.core.model.Nothing
+import com.expediagroup.sdk.core.model.Operation
+import com.expediagroup.sdk.core.model.Response
+import com.expediagroup.sdk.core.model.exception.handle
+import com.expediagroup.sdk.xap.models.*
+import com.expediagroup.sdk.xap.models.exception.ErrorObjectMapper
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiAPIGatewayErrorException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiAPIMErrorException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiCarsErrorsException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiErrorsException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiLodgingErrorsException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiPresignedUrlResponseException
+import com.expediagroup.sdk.xap.models.exception.ExpediaGroupApiSdpAPIMErrorException
+import com.expediagroup.sdk.xap.operations.GetCarDetailsOperation
+import com.expediagroup.sdk.xap.operations.GetCarsListingsOperation
+import com.expediagroup.sdk.xap.operations.GetFeedDownloadUrlOperation
+import com.expediagroup.sdk.xap.operations.GetLodgingAvailabilityCalendarsOperation
+import com.expediagroup.sdk.xap.operations.GetLodgingDetailsOperation
+import com.expediagroup.sdk.xap.operations.GetLodgingListingsOperation
+import com.expediagroup.sdk.xap.operations.GetLodgingQuotesOperation
+import com.expediagroup.sdk.xap.operations.GetLodgingRateCalendarOperation
+import io.ktor.client.call.body
+import io.ktor.client.request.request
+import io.ktor.client.request.setBody
+import io.ktor.client.request.url
+import io.ktor.client.statement.HttpResponse
+import io.ktor.http.ContentType
+import io.ktor.http.HttpMethod
+import io.ktor.http.contentType
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.future.future
+import java.util.concurrent.CompletableFuture
+
+/**
+* The XAP Lodging Search APIs can be used by partners both booking via an Expedia website, or by partners that
+will be booking via the XAP APIs. Each API also provides pre-configured links to the Expedia website,
+the XAP Booking API, or both.
+
+*/
+
+class XapClient private constructor(clientConfiguration: XapClientConfiguration) : BaseXapClient("xap", clientConfiguration) {
+ class Builder : BaseXapClient.Builder() {
+ override fun build() =
+ XapClient(
+ XapClientConfiguration(key, secret, endpoint, requestTimeout, connectionTimeout, socketTimeout, maskedLoggingHeaders, maskedLoggingBodyFields)
+ )
+ }
+
+ companion object {
+ @JvmStatic fun builder() = Builder()
+ }
+
+ override suspend fun throwServiceException(
+ response: HttpResponse,
+ operationId: String
+ ): Unit = throw ErrorObjectMapper.process(response, operationId)
+
+ private suspend inline fun executeHttpRequest(operation: Operation): HttpResponse =
+ httpClient.request {
+ method = HttpMethod.parse(operation.method)
+ url(operation.url)
+
+ operation.params?.getHeaders()?.let {
+ headers.appendAll(it)
+ }
+
+ operation.params?.getQueryParams()?.let {
+ url.parameters.appendAll(it)
+ }
+
+ val extraHeaders =
+ buildMap {
+ put("key", configurationProvider.key ?: "")
+ }
+
+ appendHeaders(extraHeaders)
+ contentType(ContentType.Application.Json)
+ setBody(operation.requestBody)
+ }
+
+ private inline fun executeWithEmptyResponse(operation: Operation): EmptyResponse {
+ try {
+ return executeAsyncWithEmptyResponse(operation).get()
+ } catch (exception: Exception) {
+ exception.handle()
+ }
+ }
+
+ private inline fun executeAsyncWithEmptyResponse(operation: Operation): CompletableFuture =
+ GlobalScope.future(Dispatchers.IO) {
+ try {
+ val response = executeHttpRequest(operation)
+ throwIfError(response, operation.operationId)
+ EmptyResponse(response.status.value, response.headers.entries())
+ } catch (exception: Exception) {
+ exception.handle()
+ }
+ }
+
+ private inline fun execute(operation: Operation): Response {
+ try {
+ return executeAsync(operation).get()
+ } catch (exception: Exception) {
+ exception.handle()
+ }
+ }
+
+ private inline fun executeAsync(operation: Operation): CompletableFuture> =
+ GlobalScope.future(Dispatchers.IO) {
+ try {
+ val response = executeHttpRequest(operation)
+ throwIfError(response, operation.operationId)
+ Response(response.status.value, response.body(), response.headers.entries())
+ } catch (exception: Exception) {
+ exception.handle()
+ }
+ }
+
+ /**
+ * Get Extended information with a single car offer
+ * Extended information about the rates, charges, fees, and other terms associated with a single car offer.
+ * @param operation [GetCarDetailsOperation]
+ * @throws ExpediaGroupApiCarsErrorsException
+ * @throws ExpediaGroupApiAPIMErrorException
+ * @throws ExpediaGroupApiException
+ * @return a [Response] object with a body of type CarDetailsResponse
+ */
+ fun execute(operation: GetCarDetailsOperation): Response = execute(operation)
+
+ /**
+ * Get Extended information with a single car offer
+ * Extended information about the rates, charges, fees, and other terms associated with a single car offer.
+ * @param operation [GetCarDetailsOperation]
+ * @throws ExpediaGroupApiCarsErrorsException
+ * @throws ExpediaGroupApiAPIMErrorException
+ * @throws ExpediaGroupApiException
+ * @return a [CompletableFuture] object with a body of type CarDetailsResponse
+ */
+ fun executeAsync(operation: GetCarDetailsOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Search Expedia car inventory
+ * Search Expedia car inventory by date, pickup, and dropoff location to return a listing of available cars for hire.
+ * @param operation [GetCarsListingsOperation]
+ * @throws ExpediaGroupApiCarsErrorsException
+ * @throws ExpediaGroupApiAPIMErrorException
+ * @throws ExpediaGroupApiException
+ * @return a [Response] object with a body of type CarListingsResponse
+ */
+ fun execute(operation: GetCarsListingsOperation): Response = execute(operation)
+
+ /**
+ * Search Expedia car inventory
+ * Search Expedia car inventory by date, pickup, and dropoff location to return a listing of available cars for hire.
+ * @param operation [GetCarsListingsOperation]
+ * @throws ExpediaGroupApiCarsErrorsException
+ * @throws ExpediaGroupApiAPIMErrorException
+ * @throws ExpediaGroupApiException
+ * @return a [CompletableFuture] object with a body of type CarListingsResponse
+ */
+ fun executeAsync(operation: GetCarsListingsOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ *
+ * Get the Download URL and other details of the static files.
+ * @param operation [GetFeedDownloadUrlOperation]
+ * @throws ExpediaGroupApiPresignedUrlResponseException
+ * @throws ExpediaGroupApiSdpAPIMErrorException
+ * @return a [Response] object with a body of type PresignedUrlResponse
+ */
+ fun execute(operation: GetFeedDownloadUrlOperation): Response = execute(operation)
+
+ /**
+ *
+ * Get the Download URL and other details of the static files.
+ * @param operation [GetFeedDownloadUrlOperation]
+ * @throws ExpediaGroupApiPresignedUrlResponseException
+ * @throws ExpediaGroupApiSdpAPIMErrorException
+ * @return a [CompletableFuture] object with a body of type PresignedUrlResponse
+ */
+ fun executeAsync(operation: GetFeedDownloadUrlOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Get availability calendars of properties
+ * Returns the availability of each day for a range of dates for given Expedia lodging properties.
+ * @param operation [GetLodgingAvailabilityCalendarsOperation]
+ * @throws ExpediaGroupApiLodgingErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [Response] object with a body of type AvailabilityCalendarResponse
+ */
+ fun execute(operation: GetLodgingAvailabilityCalendarsOperation): Response = execute(operation)
+
+ /**
+ * Get availability calendars of properties
+ * Returns the availability of each day for a range of dates for given Expedia lodging properties.
+ * @param operation [GetLodgingAvailabilityCalendarsOperation]
+ * @throws ExpediaGroupApiLodgingErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [CompletableFuture] object with a body of type AvailabilityCalendarResponse
+ */
+ fun executeAsync(operation: GetLodgingAvailabilityCalendarsOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Get Extended information with a single property offer
+ * Extended information about the rate, charges, fees, and financial terms associated with booking a single lodging rate plan offer.
+ * @param operation [GetLodgingDetailsOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [Response] object with a body of type HotelDetailsResponse
+ */
+ fun execute(operation: GetLodgingDetailsOperation): Response = execute(operation)
+
+ /**
+ * Get Extended information with a single property offer
+ * Extended information about the rate, charges, fees, and financial terms associated with booking a single lodging rate plan offer.
+ * @param operation [GetLodgingDetailsOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [CompletableFuture] object with a body of type HotelDetailsResponse
+ */
+ fun executeAsync(operation: GetLodgingDetailsOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Search lodging inventory
+ * Search Expedia lodging inventory by Location Keyword, Region ID, Lat/Long, or Hotel ID(s) and return up to 1,000 offers in response. Provides deeplink to Expedia site to book, or rate plan info to enable API booking.
+ * @param operation [GetLodgingListingsOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [Response] object with a body of type HotelListingsResponse
+ */
+ fun execute(operation: GetLodgingListingsOperation): Response = execute(operation)
+
+ /**
+ * Search lodging inventory
+ * Search Expedia lodging inventory by Location Keyword, Region ID, Lat/Long, or Hotel ID(s) and return up to 1,000 offers in response. Provides deeplink to Expedia site to book, or rate plan info to enable API booking.
+ * @param operation [GetLodgingListingsOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [CompletableFuture] object with a body of type HotelListingsResponse
+ */
+ fun executeAsync(operation: GetLodgingListingsOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Get properties price and availability information
+ * The Lodging Quotes API will return the price and availability information for given Expedia lodging property ID(s).
+ * @param operation [GetLodgingQuotesOperation]
+ * @throws ExpediaGroupApiLodgingErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [Response] object with a body of type LodgingQuotesResponse
+ */
+ fun execute(operation: GetLodgingQuotesOperation): Response = execute(operation)
+
+ /**
+ * Get properties price and availability information
+ * The Lodging Quotes API will return the price and availability information for given Expedia lodging property ID(s).
+ * @param operation [GetLodgingQuotesOperation]
+ * @throws ExpediaGroupApiLodgingErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [CompletableFuture] object with a body of type LodgingQuotesResponse
+ */
+ fun executeAsync(operation: GetLodgingQuotesOperation): CompletableFuture> = executeAsync(operation)
+
+ /**
+ * Get rate calendar of a property
+ * The Rate Calendar API will return the lowest rate plan for a range of days for one selected Expedia lodging property.
+ * @param operation [GetLodgingRateCalendarOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [Response] object with a body of type RateCalendarResponse
+ */
+ fun execute(operation: GetLodgingRateCalendarOperation): Response = execute(operation)
+
+ /**
+ * Get rate calendar of a property
+ * The Rate Calendar API will return the lowest rate plan for a range of days for one selected Expedia lodging property.
+ * @param operation [GetLodgingRateCalendarOperation]
+ * @throws ExpediaGroupApiErrorsException
+ * @throws ExpediaGroupApiAPIGatewayErrorException
+ * @return a [CompletableFuture] object with a body of type RateCalendarResponse
+ */
+ fun executeAsync(operation: GetLodgingRateCalendarOperation): CompletableFuture> = executeAsync(operation)
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/infrastructure/ApiAbstractions.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/infrastructure/ApiAbstractions.kt
new file mode 100644
index 000000000..ae25a7478
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/infrastructure/ApiAbstractions.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.xap.infrastructure
+
+typealias MultiValueMap = MutableMap>
+
+fun collectionDelimiter(collectionFormat: String) =
+ when (collectionFormat) {
+ "csv" -> ","
+ "tsv" -> "\t"
+ "pipe" -> "|"
+ "space" -> " "
+ else -> ""
+ }
+
+val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" }
+
+fun toMultiValue(
+ items: Array,
+ collectionFormat: String,
+ map: (item: T) -> String = defaultMultiValueConverter
+) = toMultiValue(items.asIterable(), collectionFormat, map)
+
+fun toMultiValue(
+ items: Iterable,
+ collectionFormat: String,
+ map: (item: T) -> String = defaultMultiValueConverter
+): List =
+ when (collectionFormat) {
+ "multi" -> items.map(map)
+ else -> listOf(items.joinToString(separator = collectionDelimiter(collectionFormat), transform = map))
+ }
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIGatewayError.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIGatewayError.kt
new file mode 100644
index 000000000..09112ca39
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIGatewayError.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param message
+ */
+data class APIGatewayError(
+ @JsonProperty("message")
+ @field:Valid
+ val message: kotlin.String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var message: kotlin.String? = null
+ ) {
+ fun message(message: kotlin.String?) = apply { this.message = message }
+
+ fun build(): APIGatewayError {
+ val instance =
+ APIGatewayError(
+ message = message
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: APIGatewayError) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ message = message
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIMError.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIMError.kt
new file mode 100644
index 000000000..25345be0b
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/APIMError.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param message
+ */
+data class APIMError(
+ @JsonProperty("message")
+ @field:Valid
+ val message: kotlin.String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var message: kotlin.String? = null
+ ) {
+ fun message(message: kotlin.String?) = apply { this.message = message }
+
+ fun build(): APIMError {
+ val instance =
+ APIMError(
+ message = message
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: APIMError) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ message = message
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AdditionalFee.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AdditionalFee.kt
new file mode 100644
index 000000000..30241211d
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AdditionalFee.kt
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.CarsMoney
+import com.expediagroup.sdk.xap.models.Deductible
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+import javax.validation.constraints.NotNull
+
+/**
+ * List of additional fees including both mandatory and optional fees.such as young driver fee/drop off fee /CollisionDamageWaiver
+ * @param isRequired Indicates whether this additional fee is mandatory.
+ * @param financeCategory Category of the fee / Coverages
+ * @param financeSubCategory Sub category of the fee / Coverages .
+ * @param amount
+ * @param description Description of the fee.
+ * @param deductible
+ */
+data class AdditionalFee(
+ // Indicates whether this additional fee is mandatory.
+ @JsonProperty("IsRequired")
+ @field:NotNull
+ @field:Valid
+ val isRequired: kotlin.Boolean,
+ // Category of the fee / Coverages
+ @JsonProperty("FinanceCategory")
+ @field:NotNull
+ @field:Valid
+ val financeCategory: kotlin.String,
+ // Sub category of the fee / Coverages .
+ @JsonProperty("FinanceSubCategory")
+ @field:NotNull
+ @field:Valid
+ val financeSubCategory: kotlin.String,
+ @JsonProperty("Amount")
+ @field:NotNull
+ @field:Valid
+ val amount: CarsMoney,
+ // Description of the fee.
+ @JsonProperty("Description")
+ @field:Valid
+ val description: kotlin.String? = null,
+ @JsonProperty("Deductible")
+ @field:Valid
+ val deductible: Deductible? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var isRequired: kotlin.Boolean? = null,
+ private var financeCategory: kotlin.String? = null,
+ private var financeSubCategory: kotlin.String? = null,
+ private var amount: CarsMoney? = null,
+ private var description: kotlin.String? = null,
+ private var deductible: Deductible? = null
+ ) {
+ fun isRequired(isRequired: kotlin.Boolean) = apply { this.isRequired = isRequired }
+
+ fun financeCategory(financeCategory: kotlin.String) = apply { this.financeCategory = financeCategory }
+
+ fun financeSubCategory(financeSubCategory: kotlin.String) = apply { this.financeSubCategory = financeSubCategory }
+
+ fun amount(amount: CarsMoney) = apply { this.amount = amount }
+
+ fun description(description: kotlin.String?) = apply { this.description = description }
+
+ fun deductible(deductible: Deductible?) = apply { this.deductible = deductible }
+
+ fun build(): AdditionalFee {
+ val instance =
+ AdditionalFee(
+ isRequired = isRequired!!,
+ financeCategory = financeCategory!!,
+ financeSubCategory = financeSubCategory!!,
+ amount = amount!!,
+ description = description,
+ deductible = deductible
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: AdditionalFee) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ isRequired = isRequired!!,
+ financeCategory = financeCategory!!,
+ financeSubCategory = financeSubCategory!!,
+ amount = amount!!,
+ description = description,
+ deductible = deductible
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Address.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Address.kt
new file mode 100644
index 000000000..d10f7d59c
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Address.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ * The address information of the location.
+ * @param address1 Street Number, Street Name, or PO Box
+ * @param address2 Apartment, Floor, Suite, Bldg or more specific information about Address1.
+ * @param city The city
+ * @param province The state or province
+ * @param country 3-letter code for the country
+ * @param postalCode Zip/postal code
+ */
+data class Address(
+ // Street Number, Street Name, or PO Box
+ @JsonProperty("Address1")
+ @field:Valid
+ val address1: kotlin.String? = null,
+ // Apartment, Floor, Suite, Bldg or more specific information about Address1.
+ @JsonProperty("Address2")
+ @field:Valid
+ val address2: kotlin.String? = null,
+ // The city
+ @JsonProperty("City")
+ @field:Valid
+ val city: kotlin.String? = null,
+ // The state or province
+ @JsonProperty("Province")
+ @field:Valid
+ val province: kotlin.String? = null,
+ // 3-letter code for the country
+ @JsonProperty("Country")
+ @field:Valid
+ val country: kotlin.String? = null,
+ // Zip/postal code
+ @JsonProperty("PostalCode")
+ @field:Valid
+ val postalCode: kotlin.String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var address1: kotlin.String? = null,
+ private var address2: kotlin.String? = null,
+ private var city: kotlin.String? = null,
+ private var province: kotlin.String? = null,
+ private var country: kotlin.String? = null,
+ private var postalCode: kotlin.String? = null
+ ) {
+ fun address1(address1: kotlin.String?) = apply { this.address1 = address1 }
+
+ fun address2(address2: kotlin.String?) = apply { this.address2 = address2 }
+
+ fun city(city: kotlin.String?) = apply { this.city = city }
+
+ fun province(province: kotlin.String?) = apply { this.province = province }
+
+ fun country(country: kotlin.String?) = apply { this.country = country }
+
+ fun postalCode(postalCode: kotlin.String?) = apply { this.postalCode = postalCode }
+
+ fun build(): Address {
+ val instance =
+ Address(
+ address1 = address1,
+ address2 = address2,
+ city = city,
+ province = province,
+ country = country,
+ postalCode = postalCode
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: Address) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ address1 = address1,
+ address2 = address2,
+ city = city,
+ province = province,
+ country = country,
+ postalCode = postalCode
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AgeClassRestriction.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AgeClassRestriction.kt
new file mode 100644
index 000000000..a3c948d70
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AgeClassRestriction.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Validation
+
+/**
+ * Container for room occupancy rules based on the age of the guests.
+ * @param ageClass Categories for hotel guests, based on age.
+ * @param ageMinimum The minimum age defined in a particular `AgeClass`.
+ * @param ageMaximum The maximum age defined in a particular `AgeClass`. If not specified, the `AgeClass` has no upper bound.
+ * @param maxGuestCount The max guest count allowed in a particular `AgeClass`.
+ */
+data class AgeClassRestriction(
+ // Categories for hotel guests, based on age.
+ @JsonProperty("AgeClass")
+ val ageClass: AgeClassRestriction.AgeClass? = null,
+ // The minimum age defined in a particular `AgeClass`.
+ @JsonProperty("AgeMinimum")
+ val ageMinimum: kotlin.Int? = null,
+ // The maximum age defined in a particular `AgeClass`. If not specified, the `AgeClass` has no upper bound.
+ @JsonProperty("AgeMaximum")
+ val ageMaximum: kotlin.Int? = null,
+ // The max guest count allowed in a particular `AgeClass`.
+ @JsonProperty("MaxGuestCount")
+ val maxGuestCount: kotlin.Int? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var ageClass: AgeClassRestriction.AgeClass? = null,
+ private var ageMinimum: kotlin.Int? = null,
+ private var ageMaximum: kotlin.Int? = null,
+ private var maxGuestCount: kotlin.Int? = null
+ ) {
+ fun ageClass(ageClass: AgeClassRestriction.AgeClass?) = apply { this.ageClass = ageClass }
+
+ fun ageMinimum(ageMinimum: kotlin.Int?) = apply { this.ageMinimum = ageMinimum }
+
+ fun ageMaximum(ageMaximum: kotlin.Int?) = apply { this.ageMaximum = ageMaximum }
+
+ fun maxGuestCount(maxGuestCount: kotlin.Int?) = apply { this.maxGuestCount = maxGuestCount }
+
+ fun build(): AgeClassRestriction {
+ val instance =
+ AgeClassRestriction(
+ ageClass = ageClass,
+ ageMinimum = ageMinimum,
+ ageMaximum = ageMaximum,
+ maxGuestCount = maxGuestCount
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: AgeClassRestriction) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ ageClass = ageClass,
+ ageMinimum = ageMinimum,
+ ageMaximum = ageMaximum,
+ maxGuestCount = maxGuestCount
+ )
+
+ /**
+ * Categories for hotel guests, based on age.
+ * Values: ALL_AGES,SENIOR,ADULT,CHILD,INFANT,OTHER
+ */
+ enum class AgeClass(val value: kotlin.String) {
+ @JsonProperty("All Ages")
+ ALL_AGES("All Ages"),
+
+ @JsonProperty("Senior")
+ SENIOR("Senior"),
+
+ @JsonProperty("Adult")
+ ADULT("Adult"),
+
+ @JsonProperty("Child")
+ CHILD("Child"),
+
+ @JsonProperty("Infant")
+ INFANT("Infant"),
+
+ @JsonProperty("Other")
+ OTHER("Other")
+ }
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendar.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendar.kt
new file mode 100644
index 000000000..9e0076e00
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendar.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.DateRange
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param propertyId The unique property identifier that designates a single property.
+ * @param dateRange
+ * @param availability A string of codes that shows property availability, one for every day in the specified date range. Valid values include Y (available) and N (unavailable). ***Note**: The first code stands for availability on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`.*
+ * @param changeOver A string of codes that shows changeover action, one for every day in the specified date range. Valid values include - X (no action possible) - C (check-in, checkout) - O (checkout only) - I (check-in only) ***Note**: The first code stands for possible action on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. All actions are possible if not returned.*
+ * @param minPriorNotify A comma-separated list of numbers that shows how many days before a reservation the booking must occur, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no prior notification required for a given day. The unit is always day. ***Note**: The first number stands for the minimum advance booking days on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.*
+ * @param minStay A comma-separated list of numbers that show the minimum number of days a traveler can stay, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no minimum for a given day. The unit is always day. ***Note**: The first number stands for the minimum stay on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.*
+ * @param maxStay A comma-separated list of numbers that show the maximum number of days a traveler can stay, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no maximum for a given day. The unit is always day. ***Note**: The first number stands for the maximum stay on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.
+ */
+data class AvailabilityCalendar(
+ // The unique property identifier that designates a single property.
+ @JsonProperty("PropertyId")
+ @field:Valid
+ val propertyId: kotlin.String? = null,
+ @JsonProperty("DateRange")
+ @field:Valid
+ val dateRange: DateRange? = null,
+ // A string of codes that shows property availability, one for every day in the specified date range. Valid values include Y (available) and N (unavailable). ***Note**: The first code stands for availability on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`.*
+ @JsonProperty("Availability")
+ @field:Valid
+ val availability: kotlin.String? = null,
+ // A string of codes that shows changeover action, one for every day in the specified date range. Valid values include - X (no action possible) - C (check-in, checkout) - O (checkout only) - I (check-in only) ***Note**: The first code stands for possible action on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. All actions are possible if not returned.*
+ @JsonProperty("ChangeOver")
+ @field:Valid
+ val changeOver: kotlin.String? = null,
+ // A comma-separated list of numbers that shows how many days before a reservation the booking must occur, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no prior notification required for a given day. The unit is always day. ***Note**: The first number stands for the minimum advance booking days on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.*
+ @JsonProperty("MinPriorNotify")
+ @field:Valid
+ val minPriorNotify: kotlin.String? = null,
+ // A comma-separated list of numbers that show the minimum number of days a traveler can stay, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no minimum for a given day. The unit is always day. ***Note**: The first number stands for the minimum stay on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.*
+ @JsonProperty("MinStay")
+ @field:Valid
+ val minStay: kotlin.String? = null,
+ // A comma-separated list of numbers that show the maximum number of days a traveler can stay, one for every day in the specified date range. Valid values include 0-999, and 0 indicates no maximum for a given day. The unit is always day. ***Note**: The first number stands for the maximum stay on the `StartDate` in the `DateRange` and the last one stands for the `EndDate`. No limitation if not returned.
+ @JsonProperty("MaxStay")
+ @field:Valid
+ val maxStay: kotlin.String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var propertyId: kotlin.String? = null,
+ private var dateRange: DateRange? = null,
+ private var availability: kotlin.String? = null,
+ private var changeOver: kotlin.String? = null,
+ private var minPriorNotify: kotlin.String? = null,
+ private var minStay: kotlin.String? = null,
+ private var maxStay: kotlin.String? = null
+ ) {
+ fun propertyId(propertyId: kotlin.String?) = apply { this.propertyId = propertyId }
+
+ fun dateRange(dateRange: DateRange?) = apply { this.dateRange = dateRange }
+
+ fun availability(availability: kotlin.String?) = apply { this.availability = availability }
+
+ fun changeOver(changeOver: kotlin.String?) = apply { this.changeOver = changeOver }
+
+ fun minPriorNotify(minPriorNotify: kotlin.String?) = apply { this.minPriorNotify = minPriorNotify }
+
+ fun minStay(minStay: kotlin.String?) = apply { this.minStay = minStay }
+
+ fun maxStay(maxStay: kotlin.String?) = apply { this.maxStay = maxStay }
+
+ fun build(): AvailabilityCalendar {
+ val instance =
+ AvailabilityCalendar(
+ propertyId = propertyId,
+ dateRange = dateRange,
+ availability = availability,
+ changeOver = changeOver,
+ minPriorNotify = minPriorNotify,
+ minStay = minStay,
+ maxStay = maxStay
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: AvailabilityCalendar) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ propertyId = propertyId,
+ dateRange = dateRange,
+ availability = availability,
+ changeOver = changeOver,
+ minPriorNotify = minPriorNotify,
+ minStay = minStay,
+ maxStay = maxStay
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendarResponse.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendarResponse.kt
new file mode 100644
index 000000000..1539de6b9
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/AvailabilityCalendarResponse.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.AvailabilityCalendar
+import com.expediagroup.sdk.xap.models.LodgingWarning
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param warnings There were some errors or events during the transaction, but the API has still returned a response. Container for all warnings.
+ * @param transactionId Unique identifier for the transaction.
+ * @param availabilityCalendars A list of the calendar entities.
+ */
+data class AvailabilityCalendarResponse(
+ // There were some errors or events during the transaction, but the API has still returned a response. Container for all warnings.
+ @JsonProperty("Warnings")
+ @field:Valid
+ val warnings: kotlin.collections.List? = null,
+ // Unique identifier for the transaction.
+ @JsonProperty("TransactionId")
+ @field:Valid
+ val transactionId: kotlin.String? = null,
+ // A list of the calendar entities.
+ @JsonProperty("AvailabilityCalendars")
+ @field:Valid
+ val availabilityCalendars: kotlin.collections.List? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var warnings: kotlin.collections.List? = null,
+ private var transactionId: kotlin.String? = null,
+ private var availabilityCalendars: kotlin.collections.List? = null
+ ) {
+ fun warnings(warnings: kotlin.collections.List?) = apply { this.warnings = warnings }
+
+ fun transactionId(transactionId: kotlin.String?) = apply { this.transactionId = transactionId }
+
+ fun availabilityCalendars(availabilityCalendars: kotlin.collections.List?) = apply { this.availabilityCalendars = availabilityCalendars }
+
+ fun build(): AvailabilityCalendarResponse {
+ val instance =
+ AvailabilityCalendarResponse(
+ warnings = warnings,
+ transactionId = transactionId,
+ availabilityCalendars = availabilityCalendars
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: AvailabilityCalendarResponse) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ warnings = warnings,
+ transactionId = transactionId,
+ availabilityCalendars = availabilityCalendars
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/BedType.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/BedType.kt
new file mode 100644
index 000000000..8dd01d693
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/BedType.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ * Statement of bed types available for this offer. A room may have several bed type options available. **NOTE**: due to the large number of bed type options available, we no longer publish a list of available bed types. More information is available in [Lodging Bed Types](https://developers.expediagroup.com/xap/products/xap/lodging/references/bed-types).
+ * @param id The bed type ID
+ * @param description The bed type description.
+ */
+data class BedType(
+ // The bed type ID
+ @JsonProperty("Id")
+ @field:Valid
+ val id: kotlin.String? = null,
+ // The bed type description.
+ @JsonProperty("Description")
+ @field:Valid
+ val description: kotlin.String? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var id: kotlin.String? = null,
+ private var description: kotlin.String? = null
+ ) {
+ fun id(id: kotlin.String?) = apply { this.id = id }
+
+ fun description(description: kotlin.String?) = apply { this.description = description }
+
+ fun build(): BedType {
+ val instance =
+ BedType(
+ id = id,
+ description = description
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: BedType) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ id = id,
+ description = description
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRule.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRule.kt
new file mode 100644
index 000000000..813dd4764
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRule.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.CancellationPenaltyRulePenaltyPrice
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param penaltyNightCount Specifies the per-stay cancellation fee charged in terms of the cost of the number of nights listed, in addition to any other penalties. The rate charged is based on the earliest night(s) of the stay.
+ * @param penaltyPercentOfStay Specifies the per-stay cancellation fee charged as a percentage of the total rate, in addition to any other penalties listed.
+ * @param penaltyPrice
+ * @param penaltyStartDateTime The beginning of the window of time when the `CancellationPenaltyRule` is in effect. The date and time are expressed in ISO 8601 International Date format, and local to the hotel.
+ * @param penaltyEndDateTime The end of the window of time when the `CancellationPenaltyRule` is in effect. The date and time are expressed in ISO 8601 International Date format, and local to the hotel.
+ */
+data class CancellationPenaltyRule(
+ // Specifies the per-stay cancellation fee charged in terms of the cost of the number of nights listed, in addition to any other penalties. The rate charged is based on the earliest night(s) of the stay.
+ @JsonProperty("PenaltyNightCount")
+ val penaltyNightCount: kotlin.Int? = null,
+ // Specifies the per-stay cancellation fee charged as a percentage of the total rate, in addition to any other penalties listed.
+ @JsonProperty("PenaltyPercentOfStay")
+ @field:Valid
+ val penaltyPercentOfStay: kotlin.String? = null,
+ @JsonProperty("PenaltyPrice")
+ @field:Valid
+ val penaltyPrice: CancellationPenaltyRulePenaltyPrice? = null,
+ // The beginning of the window of time when the `CancellationPenaltyRule` is in effect. The date and time are expressed in ISO 8601 International Date format, and local to the hotel.
+ @JsonProperty("PenaltyStartDateTime")
+ val penaltyStartDateTime: java.time.OffsetDateTime? = null,
+ // The end of the window of time when the `CancellationPenaltyRule` is in effect. The date and time are expressed in ISO 8601 International Date format, and local to the hotel.
+ @JsonProperty("PenaltyEndDateTime")
+ val penaltyEndDateTime: java.time.OffsetDateTime? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var penaltyNightCount: kotlin.Int? = null,
+ private var penaltyPercentOfStay: kotlin.String? = null,
+ private var penaltyPrice: CancellationPenaltyRulePenaltyPrice? = null,
+ private var penaltyStartDateTime: java.time.OffsetDateTime? = null,
+ private var penaltyEndDateTime: java.time.OffsetDateTime? = null
+ ) {
+ fun penaltyNightCount(penaltyNightCount: kotlin.Int?) = apply { this.penaltyNightCount = penaltyNightCount }
+
+ fun penaltyPercentOfStay(penaltyPercentOfStay: kotlin.String?) = apply { this.penaltyPercentOfStay = penaltyPercentOfStay }
+
+ fun penaltyPrice(penaltyPrice: CancellationPenaltyRulePenaltyPrice?) = apply { this.penaltyPrice = penaltyPrice }
+
+ fun penaltyStartDateTime(penaltyStartDateTime: java.time.OffsetDateTime?) = apply { this.penaltyStartDateTime = penaltyStartDateTime }
+
+ fun penaltyEndDateTime(penaltyEndDateTime: java.time.OffsetDateTime?) = apply { this.penaltyEndDateTime = penaltyEndDateTime }
+
+ fun build(): CancellationPenaltyRule {
+ val instance =
+ CancellationPenaltyRule(
+ penaltyNightCount = penaltyNightCount,
+ penaltyPercentOfStay = penaltyPercentOfStay,
+ penaltyPrice = penaltyPrice,
+ penaltyStartDateTime = penaltyStartDateTime,
+ penaltyEndDateTime = penaltyEndDateTime
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: CancellationPenaltyRule) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ penaltyNightCount = penaltyNightCount,
+ penaltyPercentOfStay = penaltyPercentOfStay,
+ penaltyPrice = penaltyPrice,
+ penaltyStartDateTime = penaltyStartDateTime,
+ penaltyEndDateTime = penaltyEndDateTime
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRulePenaltyPrice.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRulePenaltyPrice.kt
new file mode 100644
index 000000000..448d38b1f
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPenaltyRulePenaltyPrice.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.Money
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ *
+ * @param `value` The value of the element being defined.
+ * @param currency The ISO 4217 Currency Code that the value is expressed in.
+ * @param localCurrencyPrice
+ */
+data class CancellationPenaltyRulePenaltyPrice(
+ // The value of the element being defined.
+ @JsonProperty("Value")
+ @field:Valid
+ val `value`: kotlin.String? = null,
+ // The ISO 4217 Currency Code that the value is expressed in.
+ @JsonProperty("Currency")
+ @field:Valid
+ val currency: kotlin.String? = null,
+ @JsonProperty("LocalCurrencyPrice")
+ @field:Valid
+ val localCurrencyPrice: Money? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var `value`: kotlin.String? = null,
+ private var currency: kotlin.String? = null,
+ private var localCurrencyPrice: Money? = null
+ ) {
+ fun `value`(`value`: kotlin.String?) = apply { this.`value` = `value` }
+
+ fun currency(currency: kotlin.String?) = apply { this.currency = currency }
+
+ fun localCurrencyPrice(localCurrencyPrice: Money?) = apply { this.localCurrencyPrice = localCurrencyPrice }
+
+ fun build(): CancellationPenaltyRulePenaltyPrice {
+ val instance =
+ CancellationPenaltyRulePenaltyPrice(
+ `value` = `value`,
+ currency = currency,
+ localCurrencyPrice = localCurrencyPrice
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: CancellationPenaltyRulePenaltyPrice) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ `value` = `value`,
+ currency = currency,
+ localCurrencyPrice = localCurrencyPrice
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPolicy.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPolicy.kt
new file mode 100644
index 000000000..5a12667ea
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CancellationPolicy.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.CancellationPenaltyRule
+import com.expediagroup.sdk.xap.models.NonRefundableDateRange
+import com.expediagroup.sdk.xap.models.WaiverPolicy
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+
+/**
+ * Container for room cancellation policy.
+ * @param waiverPolicy
+ * @param cancellableOnline Boolean value to identify if the reservation can be cancelled online. If false, the customer will only be able to cancel a refundable room by calling Expedia Customer Service.
+ * @param refundable Indicate whether the rate is refundable or not.
+ * @param freeCancellation Indicate whether the room can be cancelled free of charge.
+ * @param freeCancellationEndDateTime The date and time until which the room can be cancelled free of charge. This is expressed in the local time of the Hotel.
+ * @param cancellationPenaltyRules Container for cancellation penalty details.
+ * @param cancelPolicyDescription Additional cancellation policy information available as static text.
+ * @param nonRefundableDateRanges A list of dates ranges that are non-refundable. **Note**: The stay dates in those date ranges will always be charged whenever there is any cancellation penalty rule.
+ */
+data class CancellationPolicy(
+ @JsonProperty("WaiverPolicy")
+ @field:Valid
+ val waiverPolicy: WaiverPolicy? = null,
+ // Boolean value to identify if the reservation can be cancelled online. If false, the customer will only be able to cancel a refundable room by calling Expedia Customer Service.
+ @JsonProperty("CancellableOnline")
+ @field:Valid
+ val cancellableOnline: kotlin.Boolean? = null,
+ // Indicate whether the rate is refundable or not.
+ @JsonProperty("Refundable")
+ @field:Valid
+ val refundable: kotlin.Boolean? = null,
+ // Indicate whether the room can be cancelled free of charge.
+ @JsonProperty("FreeCancellation")
+ @field:Valid
+ val freeCancellation: kotlin.Boolean? = null,
+ // The date and time until which the room can be cancelled free of charge. This is expressed in the local time of the Hotel.
+ @JsonProperty("FreeCancellationEndDateTime")
+ val freeCancellationEndDateTime: java.time.OffsetDateTime? = null,
+ // Container for cancellation penalty details.
+ @JsonProperty("CancellationPenaltyRules")
+ @field:Valid
+ val cancellationPenaltyRules: kotlin.collections.List? = null,
+ // Additional cancellation policy information available as static text.
+ @JsonProperty("CancelPolicyDescription")
+ @field:Valid
+ val cancelPolicyDescription: kotlin.String? = null,
+ // A list of dates ranges that are non-refundable. **Note**: The stay dates in those date ranges will always be charged whenever there is any cancellation penalty rule.
+ @JsonProperty("NonRefundableDateRanges")
+ @field:Valid
+ val nonRefundableDateRanges: kotlin.collections.List? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var waiverPolicy: WaiverPolicy? = null,
+ private var cancellableOnline: kotlin.Boolean? = null,
+ private var refundable: kotlin.Boolean? = null,
+ private var freeCancellation: kotlin.Boolean? = null,
+ private var freeCancellationEndDateTime: java.time.OffsetDateTime? = null,
+ private var cancellationPenaltyRules: kotlin.collections.List? = null,
+ private var cancelPolicyDescription: kotlin.String? = null,
+ private var nonRefundableDateRanges: kotlin.collections.List? = null
+ ) {
+ fun waiverPolicy(waiverPolicy: WaiverPolicy?) = apply { this.waiverPolicy = waiverPolicy }
+
+ fun cancellableOnline(cancellableOnline: kotlin.Boolean?) = apply { this.cancellableOnline = cancellableOnline }
+
+ fun refundable(refundable: kotlin.Boolean?) = apply { this.refundable = refundable }
+
+ fun freeCancellation(freeCancellation: kotlin.Boolean?) = apply { this.freeCancellation = freeCancellation }
+
+ fun freeCancellationEndDateTime(freeCancellationEndDateTime: java.time.OffsetDateTime?) = apply { this.freeCancellationEndDateTime = freeCancellationEndDateTime }
+
+ fun cancellationPenaltyRules(cancellationPenaltyRules: kotlin.collections.List?) = apply { this.cancellationPenaltyRules = cancellationPenaltyRules }
+
+ fun cancelPolicyDescription(cancelPolicyDescription: kotlin.String?) = apply { this.cancelPolicyDescription = cancelPolicyDescription }
+
+ fun nonRefundableDateRanges(nonRefundableDateRanges: kotlin.collections.List?) = apply { this.nonRefundableDateRanges = nonRefundableDateRanges }
+
+ fun build(): CancellationPolicy {
+ val instance =
+ CancellationPolicy(
+ waiverPolicy = waiverPolicy,
+ cancellableOnline = cancellableOnline,
+ refundable = refundable,
+ freeCancellation = freeCancellation,
+ freeCancellationEndDateTime = freeCancellationEndDateTime,
+ cancellationPenaltyRules = cancellationPenaltyRules,
+ cancelPolicyDescription = cancelPolicyDescription,
+ nonRefundableDateRanges = nonRefundableDateRanges
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: CancellationPolicy) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ waiverPolicy = waiverPolicy,
+ cancellableOnline = cancellableOnline,
+ refundable = refundable,
+ freeCancellation = freeCancellation,
+ freeCancellationEndDateTime = freeCancellationEndDateTime,
+ cancellationPenaltyRules = cancellationPenaltyRules,
+ cancelPolicyDescription = cancelPolicyDescription,
+ nonRefundableDateRanges = nonRefundableDateRanges
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Capacity.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Capacity.kt
new file mode 100644
index 000000000..17fbeb6c1
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Capacity.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Validation
+
+/**
+ * Capacity for car's properties.
+ * @param adultCount The typical number of adults that can fit comfortably in the car.
+ * @param childCount The typical number of children that can fit comfortably in the car.
+ * @param smallLuggageCount The typical number of small pieces of luggage that fit in the cargo space.
+ * @param largeLuggageCount The typical number of large pieces of luggage that fit in the cargo space.
+ */
+data class Capacity(
+ // The typical number of adults that can fit comfortably in the car.
+ @JsonProperty("AdultCount")
+ val adultCount: kotlin.Long,
+ // The typical number of children that can fit comfortably in the car.
+ @JsonProperty("ChildCount")
+ val childCount: kotlin.Long? = null,
+ // The typical number of small pieces of luggage that fit in the cargo space.
+ @JsonProperty("SmallLuggageCount")
+ val smallLuggageCount: kotlin.Long? = null,
+ // The typical number of large pieces of luggage that fit in the cargo space.
+ @JsonProperty("LargeLuggageCount")
+ val largeLuggageCount: kotlin.Long? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var adultCount: kotlin.Long? = null,
+ private var childCount: kotlin.Long? = null,
+ private var smallLuggageCount: kotlin.Long? = null,
+ private var largeLuggageCount: kotlin.Long? = null
+ ) {
+ fun adultCount(adultCount: kotlin.Long) = apply { this.adultCount = adultCount }
+
+ fun childCount(childCount: kotlin.Long?) = apply { this.childCount = childCount }
+
+ fun smallLuggageCount(smallLuggageCount: kotlin.Long?) = apply { this.smallLuggageCount = smallLuggageCount }
+
+ fun largeLuggageCount(largeLuggageCount: kotlin.Long?) = apply { this.largeLuggageCount = largeLuggageCount }
+
+ fun build(): Capacity {
+ val instance =
+ Capacity(
+ adultCount = adultCount!!,
+ childCount = childCount,
+ smallLuggageCount = smallLuggageCount,
+ largeLuggageCount = largeLuggageCount
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: Capacity) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ adultCount = adultCount!!,
+ childCount = childCount,
+ smallLuggageCount = smallLuggageCount,
+ largeLuggageCount = largeLuggageCount
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Car.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Car.kt
new file mode 100644
index 000000000..2e806cdb7
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/Car.kt
@@ -0,0 +1,262 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.AdditionalFee
+import com.expediagroup.sdk.xap.models.CarsCancellationPolicy
+import com.expediagroup.sdk.xap.models.CarsLink
+import com.expediagroup.sdk.xap.models.CarsMoney
+import com.expediagroup.sdk.xap.models.Image
+import com.expediagroup.sdk.xap.models.PenaltyType
+import com.expediagroup.sdk.xap.models.Price
+import com.expediagroup.sdk.xap.models.RateDetails
+import com.expediagroup.sdk.xap.models.RatingWithoutDetails
+import com.expediagroup.sdk.xap.models.Supplier
+import com.expediagroup.sdk.xap.models.VehicleDetails
+import com.expediagroup.sdk.xap.models.VendorLocationDetails
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+import javax.validation.constraints.NotNull
+
+/**
+ * List of cars matching the search criteria.
+ * @param id Uniquely identifies a Car Offer.Note: since pay-online and pay-at-the-counter Car Offers have the same associated Rate Code, the Offer ID is the only unique identifier to differentiate between the two offers when referencing or booking.
+ * @param vehicleDetails
+ * @param supplier
+ * @param pickupDetails
+ * @param dropOffDetails
+ * @param price
+ * @param cancellationPolicy
+ * @param dataTimeStamp DataTimeStamp
+ * @param onlineCheckIn Indicate whether the supplier supports online checkin
+ * @param skipTheCounter Indicate whether the supplier supports skip the counter
+ * @param links A map of links to other Car APIs or Expedia websites.
+ * @param rateDetails
+ * @param referencePrice
+ * @param additionalFees List of additional fees including both mandatory and optional fees.such as young driver fee/drop off fee /CollisionDamageWaiver
+ * @param noShowPenalty
+ * @param images List of image resources of the car product.
+ * @param rating
+ */
+data class Car(
+ // Uniquely identifies a Car Offer.Note: since pay-online and pay-at-the-counter Car Offers have the same associated Rate Code, the Offer ID is the only unique identifier to differentiate between the two offers when referencing or booking.
+ @JsonProperty("Id")
+ @field:NotNull
+ @field:Valid
+ val id: kotlin.String,
+ @JsonProperty("VehicleDetails")
+ @field:NotNull
+ @field:Valid
+ val vehicleDetails: VehicleDetails,
+ @JsonProperty("Supplier")
+ @field:NotNull
+ @field:Valid
+ val supplier: Supplier,
+ @JsonProperty("PickupDetails")
+ @field:NotNull
+ @field:Valid
+ val pickupDetails: VendorLocationDetails,
+ @JsonProperty("DropOffDetails")
+ @field:NotNull
+ @field:Valid
+ val dropOffDetails: VendorLocationDetails,
+ @JsonProperty("Price")
+ @field:NotNull
+ @field:Valid
+ val price: Price,
+ @JsonProperty("CancellationPolicy")
+ @field:NotNull
+ @field:Valid
+ val cancellationPolicy: CarsCancellationPolicy,
+ // DataTimeStamp
+ @JsonProperty("DataTimeStamp")
+ val dataTimeStamp: java.time.OffsetDateTime? = null,
+ // Indicate whether the supplier supports online checkin
+ @JsonProperty("OnlineCheckIn")
+ @field:Valid
+ val onlineCheckIn: kotlin.Boolean? = null,
+ // Indicate whether the supplier supports skip the counter
+ @JsonProperty("SkipTheCounter")
+ @field:Valid
+ val skipTheCounter: kotlin.Boolean? = null,
+ // A map of links to other Car APIs or Expedia websites.
+ @JsonProperty("Links")
+ @field:Valid
+ val links: kotlin.collections.Map? = null,
+ @JsonProperty("RateDetails")
+ @field:Valid
+ val rateDetails: RateDetails? = null,
+ @JsonProperty("ReferencePrice")
+ @field:Valid
+ val referencePrice: CarsMoney? = null,
+ // List of additional fees including both mandatory and optional fees.such as young driver fee/drop off fee /CollisionDamageWaiver
+ @JsonProperty("AdditionalFees")
+ @field:Valid
+ val additionalFees: kotlin.collections.List? = null,
+ @JsonProperty("NoShowPenalty")
+ @field:Valid
+ val noShowPenalty: PenaltyType? = null,
+ // List of image resources of the car product.
+ @JsonProperty("Images")
+ @field:Valid
+ val images: kotlin.collections.List? = null,
+ @JsonProperty("Rating")
+ @field:Valid
+ val rating: RatingWithoutDetails? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var id: kotlin.String? = null,
+ private var vehicleDetails: VehicleDetails? = null,
+ private var supplier: Supplier? = null,
+ private var pickupDetails: VendorLocationDetails? = null,
+ private var dropOffDetails: VendorLocationDetails? = null,
+ private var price: Price? = null,
+ private var cancellationPolicy: CarsCancellationPolicy? = null,
+ private var dataTimeStamp: java.time.OffsetDateTime? = null,
+ private var onlineCheckIn: kotlin.Boolean? = null,
+ private var skipTheCounter: kotlin.Boolean? = null,
+ private var links: kotlin.collections.Map? = null,
+ private var rateDetails: RateDetails? = null,
+ private var referencePrice: CarsMoney? = null,
+ private var additionalFees: kotlin.collections.List? = null,
+ private var noShowPenalty: PenaltyType? = null,
+ private var images: kotlin.collections.List? = null,
+ private var rating: RatingWithoutDetails? = null
+ ) {
+ fun id(id: kotlin.String) = apply { this.id = id }
+
+ fun vehicleDetails(vehicleDetails: VehicleDetails) = apply { this.vehicleDetails = vehicleDetails }
+
+ fun supplier(supplier: Supplier) = apply { this.supplier = supplier }
+
+ fun pickupDetails(pickupDetails: VendorLocationDetails) = apply { this.pickupDetails = pickupDetails }
+
+ fun dropOffDetails(dropOffDetails: VendorLocationDetails) = apply { this.dropOffDetails = dropOffDetails }
+
+ fun price(price: Price) = apply { this.price = price }
+
+ fun cancellationPolicy(cancellationPolicy: CarsCancellationPolicy) = apply { this.cancellationPolicy = cancellationPolicy }
+
+ fun dataTimeStamp(dataTimeStamp: java.time.OffsetDateTime?) = apply { this.dataTimeStamp = dataTimeStamp }
+
+ fun onlineCheckIn(onlineCheckIn: kotlin.Boolean?) = apply { this.onlineCheckIn = onlineCheckIn }
+
+ fun skipTheCounter(skipTheCounter: kotlin.Boolean?) = apply { this.skipTheCounter = skipTheCounter }
+
+ fun links(links: kotlin.collections.Map?) = apply { this.links = links }
+
+ fun rateDetails(rateDetails: RateDetails?) = apply { this.rateDetails = rateDetails }
+
+ fun referencePrice(referencePrice: CarsMoney?) = apply { this.referencePrice = referencePrice }
+
+ fun additionalFees(additionalFees: kotlin.collections.List?) = apply { this.additionalFees = additionalFees }
+
+ fun noShowPenalty(noShowPenalty: PenaltyType?) = apply { this.noShowPenalty = noShowPenalty }
+
+ fun images(images: kotlin.collections.List?) = apply { this.images = images }
+
+ fun rating(rating: RatingWithoutDetails?) = apply { this.rating = rating }
+
+ fun build(): Car {
+ val instance =
+ Car(
+ id = id!!,
+ vehicleDetails = vehicleDetails!!,
+ supplier = supplier!!,
+ pickupDetails = pickupDetails!!,
+ dropOffDetails = dropOffDetails!!,
+ price = price!!,
+ cancellationPolicy = cancellationPolicy!!,
+ dataTimeStamp = dataTimeStamp,
+ onlineCheckIn = onlineCheckIn,
+ skipTheCounter = skipTheCounter,
+ links = links,
+ rateDetails = rateDetails,
+ referencePrice = referencePrice,
+ additionalFees = additionalFees,
+ noShowPenalty = noShowPenalty,
+ images = images,
+ rating = rating
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: Car) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ id = id!!,
+ vehicleDetails = vehicleDetails!!,
+ supplier = supplier!!,
+ pickupDetails = pickupDetails!!,
+ dropOffDetails = dropOffDetails!!,
+ price = price!!,
+ cancellationPolicy = cancellationPolicy!!,
+ dataTimeStamp = dataTimeStamp,
+ onlineCheckIn = onlineCheckIn,
+ skipTheCounter = skipTheCounter,
+ links = links,
+ rateDetails = rateDetails,
+ referencePrice = referencePrice,
+ additionalFees = additionalFees,
+ noShowPenalty = noShowPenalty,
+ images = images,
+ rating = rating
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarCategory.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarCategory.kt
new file mode 100644
index 000000000..df984b321
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarCategory.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+import javax.validation.constraints.NotNull
+
+/**
+ * Car category. Please find list of Car Type Codes in https://expediaintegration.zendesk.com/hc/en-us/articles/115008631767
+ * @param code Car category code.
+ * @param `value` Car category value.
+ */
+data class CarCategory(
+ // Car category code.
+ @JsonProperty("Code")
+ @field:NotNull
+ @field:Valid
+ val code: kotlin.String,
+ // Car category value.
+ @JsonProperty("Value")
+ @field:NotNull
+ @field:Valid
+ val `value`: kotlin.String
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var code: kotlin.String? = null,
+ private var `value`: kotlin.String? = null
+ ) {
+ fun code(code: kotlin.String) = apply { this.code = code }
+
+ fun `value`(`value`: kotlin.String) = apply { this.`value` = `value` }
+
+ fun build(): CarCategory {
+ val instance =
+ CarCategory(
+ code = code!!,
+ `value` = `value`!!
+ )
+
+ validate(instance)
+
+ return instance
+ }
+
+ private fun validate(instance: CarCategory) {
+ val validator =
+ Validation
+ .byDefaultProvider()
+ .configure()
+ .messageInterpolator(ParameterMessageInterpolator())
+ .buildValidatorFactory()
+ .validator
+
+ val violations = validator.validate(instance)
+
+ if (violations.isNotEmpty()) {
+ throw PropertyConstraintViolationException(
+ constraintViolations = violations.map { "${it.propertyPath}: ${it.message}" }
+ )
+ }
+ }
+ }
+
+ fun toBuilder() =
+ Builder(
+ code = code!!,
+ `value` = `value`!!
+ )
+}
diff --git a/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarDetails.kt b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarDetails.kt
new file mode 100644
index 000000000..e2047620e
--- /dev/null
+++ b/code/src/main/kotlin/com/expediagroup/sdk/xap/models/CarDetails.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Please note:
+ * This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * Do not edit this file manually.
+ *
+ */
+
+@file:Suppress(
+ "ArrayInDataClass",
+ "EnumEntryName",
+ "RemoveRedundantQualifierName",
+ "UnusedImport"
+)
+
+package com.expediagroup.sdk.xap.models
+
+import com.expediagroup.sdk.core.model.exception.client.PropertyConstraintViolationException
+import com.expediagroup.sdk.xap.models.AdditionalFee
+import com.expediagroup.sdk.xap.models.CarPolicy
+import com.expediagroup.sdk.xap.models.CarsCancellationPolicy
+import com.expediagroup.sdk.xap.models.CarsMoney
+import com.expediagroup.sdk.xap.models.Equipment
+import com.expediagroup.sdk.xap.models.ExtraFees
+import com.expediagroup.sdk.xap.models.Image
+import com.expediagroup.sdk.xap.models.PenaltyType
+import com.expediagroup.sdk.xap.models.Price
+import com.expediagroup.sdk.xap.models.RateDetails
+import com.expediagroup.sdk.xap.models.Rating
+import com.expediagroup.sdk.xap.models.RentalLimits
+import com.expediagroup.sdk.xap.models.Supplier
+import com.expediagroup.sdk.xap.models.TaxesAndFees
+import com.expediagroup.sdk.xap.models.VehicleDetails
+import com.expediagroup.sdk.xap.models.VendorLocationDetails
+import com.fasterxml.jackson.annotation.JsonProperty
+import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator
+import javax.validation.Valid
+import javax.validation.Validation
+import javax.validation.constraints.NotNull
+
+/**
+ * Details of requested car.
+ * @param vehicleDetails
+ * @param supplier
+ * @param pickupDetails
+ * @param dropOffDetails
+ * @param price
+ * @param cancellationPolicy
+ * @param onlineCheckIn Indicate whether the supplier supports online checkin
+ * @param skipTheCounter Indicate whether the supplier supports skip the counter
+ * @param rateDetails
+ * @param referencePrice
+ * @param additionalFees List of additional fees including both mandatory and optional fees.such as young driver fee/drop off fee /CollisionDamageWaiver
+ * @param taxesAndFeesDetails List of TaxesAndFees Details
+ * @param extraFeesDetails List of ExtraFeesDetails
+ * @param specialEquipments Description and costs of any optional special equipment that may be rented with the car.
+ * @param rentalLimits
+ * @param noShowPenalty
+ * @param carPolicies A list of policies that apply to this car rental.
+ * @param images List of image resources of the car product.
+ * @param rating
+ */
+data class CarDetails(
+ @JsonProperty("VehicleDetails")
+ @field:NotNull
+ @field:Valid
+ val vehicleDetails: VehicleDetails,
+ @JsonProperty("Supplier")
+ @field:NotNull
+ @field:Valid
+ val supplier: Supplier,
+ @JsonProperty("PickupDetails")
+ @field:NotNull
+ @field:Valid
+ val pickupDetails: VendorLocationDetails,
+ @JsonProperty("DropOffDetails")
+ @field:NotNull
+ @field:Valid
+ val dropOffDetails: VendorLocationDetails,
+ @JsonProperty("Price")
+ @field:NotNull
+ @field:Valid
+ val price: Price,
+ @JsonProperty("CancellationPolicy")
+ @field:NotNull
+ @field:Valid
+ val cancellationPolicy: CarsCancellationPolicy,
+ // Indicate whether the supplier supports online checkin
+ @JsonProperty("OnlineCheckIn")
+ @field:Valid
+ val onlineCheckIn: kotlin.Boolean? = null,
+ // Indicate whether the supplier supports skip the counter
+ @JsonProperty("SkipTheCounter")
+ @field:Valid
+ val skipTheCounter: kotlin.Boolean? = null,
+ @JsonProperty("RateDetails")
+ @field:Valid
+ val rateDetails: RateDetails? = null,
+ @JsonProperty("ReferencePrice")
+ @field:Valid
+ val referencePrice: CarsMoney? = null,
+ // List of additional fees including both mandatory and optional fees.such as young driver fee/drop off fee /CollisionDamageWaiver
+ @JsonProperty("AdditionalFees")
+ @field:Valid
+ val additionalFees: kotlin.collections.List? = null,
+ // List of TaxesAndFees Details
+ @JsonProperty("TaxesAndFeesDetails")
+ @field:Valid
+ val taxesAndFeesDetails: kotlin.collections.List? = null,
+ // List of ExtraFeesDetails
+ @JsonProperty("ExtraFeesDetails")
+ @field:Valid
+ val extraFeesDetails: kotlin.collections.List? = null,
+ // Description and costs of any optional special equipment that may be rented with the car.
+ @JsonProperty("SpecialEquipments")
+ @field:Valid
+ val specialEquipments: kotlin.collections.List? = null,
+ @JsonProperty("RentalLimits")
+ @field:Valid
+ val rentalLimits: RentalLimits? = null,
+ @JsonProperty("NoShowPenalty")
+ @field:Valid
+ val noShowPenalty: PenaltyType? = null,
+ // A list of policies that apply to this car rental.
+ @JsonProperty("CarPolicies")
+ @field:Valid
+ val carPolicies: kotlin.collections.List? = null,
+ // List of image resources of the car product.
+ @JsonProperty("Images")
+ @field:Valid
+ val images: kotlin.collections.List? = null,
+ @JsonProperty("Rating")
+ @field:Valid
+ val rating: Rating? = null
+) {
+ companion object {
+ @JvmStatic
+ fun builder() = Builder()
+ }
+
+ class Builder(
+ private var vehicleDetails: VehicleDetails? = null,
+ private var supplier: Supplier? = null,
+ private var pickupDetails: VendorLocationDetails? = null,
+ private var dropOffDetails: VendorLocationDetails? = null,
+ private var price: Price? = null,
+ private var cancellationPolicy: CarsCancellationPolicy? = null,
+ private var onlineCheckIn: kotlin.Boolean? = null,
+ private var skipTheCounter: kotlin.Boolean? = null,
+ private var rateDetails: RateDetails? = null,
+ private var referencePrice: CarsMoney? = null,
+ private var additionalFees: kotlin.collections.List? = null,
+ private var taxesAndFeesDetails: kotlin.collections.List? = null,
+ private var extraFeesDetails: kotlin.collections.List? = null,
+ private var specialEquipments: kotlin.collections.List? = null,
+ private var rentalLimits: RentalLimits? = null,
+ private var noShowPenalty: PenaltyType? = null,
+ private var carPolicies: kotlin.collections.List? = null,
+ private var images: kotlin.collections.List? = null,
+ private var rating: Rating? = null
+ ) {
+ fun vehicleDetails(vehicleDetails: VehicleDetails) = apply { this.vehicleDetails = vehicleDetails }
+
+ fun supplier(supplier: Supplier) = apply { this.supplier = supplier }
+
+ fun pickupDetails(pickupDetails: VendorLocationDetails) = apply { this.pickupDetails = pickupDetails }
+
+ fun dropOffDetails(dropOffDetails: VendorLocationDetails) = apply { this.dropOffDetails = dropOffDetails }
+
+ fun price(price: Price) = apply { this.price = price }
+
+ fun cancellationPolicy(cancellationPolicy: CarsCancellationPolicy) = apply { this.cancellationPolicy = cancellationPolicy }
+
+ fun onlineCheckIn(onlineCheckIn: kotlin.Boolean?) = apply { this.onlineCheckIn = onlineCheckIn }
+
+ fun skipTheCounter(skipTheCounter: kotlin.Boolean?) = apply { this.skipTheCounter = skipTheCounter }
+
+ fun rateDetails(rateDetails: RateDetails?) = apply { this.rateDetails = rateDetails }
+
+ fun referencePrice(referencePrice: CarsMoney?) = apply { this.referencePrice = referencePrice }
+
+ fun additionalFees(additionalFees: kotlin.collections.List?) = apply { this.additionalFees = additionalFees }
+
+ fun taxesAndFeesDetails(taxesAndFeesDetails: kotlin.collections.List?) = apply { this.taxesAndFeesDetails = taxesAndFeesDetails }
+
+ fun extraFeesDetails(extraFeesDetails: kotlin.collections.List?) = apply { this.extraFeesDetails = extraFeesDetails }
+
+ fun specialEquipments(specialEquipments: kotlin.collections.List?) = apply { this.specialEquipments = specialEquipments }
+
+ fun rentalLimits(rentalLimits: RentalLimits?) = apply { this.rentalLimits = rentalLimits }
+
+ fun noShowPenalty(noShowPenalty: PenaltyType?) = apply { this.noShowPenalty = noShowPenalty }
+
+ fun carPolicies(carPolicies: kotlin.collections.List