Refactor rest clients and support timeouts 74/130374/4
authorLukasz Rajewski <lukasz.rajewski@t-mobile.pl>
Thu, 18 Aug 2022 19:50:31 +0000 (21:50 +0200)
committerLukasz Rajewski <lukasz.rajewski@t-mobile.pl>
Wed, 24 Aug 2022 14:48:20 +0000 (14:48 +0000)
- Refactored rest clients to remove redundant code
- Timeouts added to the configuration of rest clients

Issue-ID: CCSDK-3716
Signed-off-by: Lukasz Rajewski <lukasz.rajewski@t-mobile.pl>
Change-Id: I706b8efd8447570455b8b65bd5b1a22da474f62b

ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/K8sAbstractRestClientService.kt
ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockBlueprintWebClientService.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/BluePrintRestLibData.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt [new file with mode: 0644]
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BlueprintWebClientService.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/SSLRestClientService.kt
ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/TokenAuthRestClientService.kt

index 14f14f6..b9c45e4 100644 (file)
 
 package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s
 
-import org.apache.http.message.BasicHeader
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
-import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
-import org.springframework.http.HttpHeaders.ACCEPT
-import org.springframework.http.HttpHeaders.AUTHORIZATION
-import org.springframework.http.HttpHeaders.CONTENT_TYPE
-import org.springframework.http.MediaType.APPLICATION_JSON_VALUE
-import java.nio.charset.Charset
-import java.util.Base64
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BasicAuthRestClientService
 
 abstract class K8sAbstractRestClientService(
     private val k8sConfiguration: K8sConnectionPluginConfiguration
-) : BlueprintWebClientService {
+) : BasicAuthRestClientService(BasicAuthRestClientProperties()) {
 
     protected val baseUrl: String = k8sConfiguration.getProperties().url
     private var restClientProperties: BasicAuthRestClientProperties? = null
 
+    override fun getRestClientProperties(): BasicAuthRestClientProperties {
+        return getBasicAuthRestClientProperties()
+    }
+
     private fun getBasicAuthRestClientProperties(): BasicAuthRestClientProperties {
         return if (restClientProperties != null)
             restClientProperties!!
@@ -55,45 +52,8 @@ abstract class K8sAbstractRestClientService(
         mapOfHeaders["Accept"] = "application/json"
         mapOfHeaders["Content-Type"] = "application/json"
         mapOfHeaders["cache-control"] = " no-cache"
-        mapOfHeaders["Accept"] = "application/json"
         return mapOfHeaders
     }
 
-    private fun setBasicAuth(username: String, password: String): String {
-        val credentialsString = "$username:$password"
-        return Base64.getEncoder().encodeToString(credentialsString.toByteArray(Charset.defaultCharset()))
-    }
-
-    override fun defaultHeaders(): Map<String, String> {
-        val encodedCredentials = setBasicAuth(
-            getBasicAuthRestClientProperties().username,
-            getBasicAuthRestClientProperties().password
-        )
-        return mapOf(
-            CONTENT_TYPE to APPLICATION_JSON_VALUE,
-            ACCEPT to APPLICATION_JSON_VALUE,
-            AUTHORIZATION to "Basic $encodedCredentials"
-        )
-    }
-
-    override fun host(uri: String): String {
-        return getBasicAuthRestClientProperties().url + uri
-    }
-
-    override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
-        val customHeaders: MutableMap<String, String> = headers.toMutableMap()
-        // inject additionalHeaders
-        customHeaders.putAll(verifyAdditionalHeaders(getBasicAuthRestClientProperties()))
-
-        if (!headers.containsKey(AUTHORIZATION)) {
-            val encodedCredentials = setBasicAuth(
-                getBasicAuthRestClientProperties().username,
-                getBasicAuthRestClientProperties().password
-            )
-            customHeaders[AUTHORIZATION] = "Basic $encodedCredentials"
-        }
-        return super.convertToBasicHeaders(customHeaders)
-    }
-
     abstract fun apiUrl(): String
 }
index 86a9ed0..ba273e1 100644 (file)
@@ -21,6 +21,7 @@ import org.mockserver.model.Header
 import org.mockserver.model.HttpRequest.request
 import org.mockserver.model.HttpResponse.response
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BaseBlueprintWebClientService
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
 import org.springframework.http.HttpHeaders
 import org.springframework.http.MediaType
@@ -28,7 +29,7 @@ import java.nio.charset.Charset
 import java.util.Base64
 
 class MockBlueprintWebClientService(private var restClientProperties: RestClientProperties) :
-    BlueprintWebClientService {
+    BaseBlueprintWebClientService<RestClientProperties>() {
 
     private var mockServer: ClientAndServer
     private var port: String = if (restClientProperties.url.split(":")[2].isEmpty()) "8080"
@@ -52,6 +53,10 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient
         )
     }
 
+    override fun getRestClientProperties(): RestClientProperties {
+        return restClientProperties
+    }
+
     override fun defaultHeaders(): Map<String, String> {
         val encodedCredentials = this.setBasicAuth("admin", "aaiTest")
         return mapOf(
@@ -61,10 +66,6 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient
         )
     }
 
-    override fun host(uri: String): String {
-        return restClientProperties.url + uri
-    }
-
     fun tearDown() {
         mockServer.close()
     }
index 01011cc..b0282c4 100644 (file)
@@ -22,6 +22,9 @@ open class RestClientProperties {
 
     lateinit var type: String
     lateinit var url: String
+    var connectTimeout: Int = 0
+    var socketTimeout: Int = 0
+    var connectionRequestTimeout: Int = 0
     var additionalHeaders: Map<String, String>? = null
 }
 
diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt
new file mode 100644 (file)
index 0000000..bc1dc4e
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation
+ * Modifications Copyright © 2018-2019 IBM.
+ * Modifications Copyright © 2019 Huawei.
+ * Modifications Copyright © 2022 Deutsche Telekom AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
+
+import com.fasterxml.jackson.databind.JsonNode
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.apache.commons.io.IOUtils
+import org.apache.http.client.ClientProtocolException
+import org.apache.http.client.config.RequestConfig
+import org.apache.http.client.methods.HttpDelete
+import org.apache.http.client.methods.HttpGet
+import org.apache.http.client.methods.HttpPatch
+import org.apache.http.client.methods.HttpPost
+import org.apache.http.client.methods.HttpPut
+import org.apache.http.client.methods.HttpUriRequest
+import org.apache.http.entity.StringEntity
+import org.apache.http.impl.client.CloseableHttpClient
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.message.BasicHeader
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
+import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
+import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import java.io.IOException
+import java.io.InputStream
+import java.net.URI
+import java.nio.charset.Charset
+
+abstract class BaseBlueprintWebClientService<out E : RestClientProperties> : BlueprintWebClientService {
+
+    open fun host(uri: String): String {
+        val uri: URI = URI.create(getRestClientProperties().url + uri)
+        return uri.resolve(uri).toString()
+    }
+
+    abstract fun getRestClientProperties(): E
+
+    open fun getRequestConfig(): RequestConfig {
+        val requestConfigBuilder = RequestConfig.custom()
+        if (getRestClientProperties().connectionRequestTimeout > 0)
+            requestConfigBuilder.setConnectionRequestTimeout(getRestClientProperties().connectionRequestTimeout)
+        if (getRestClientProperties().connectTimeout > 0)
+            requestConfigBuilder.setConnectTimeout(getRestClientProperties().connectTimeout)
+        if (getRestClientProperties().socketTimeout > 0)
+            requestConfigBuilder.setSocketTimeout(getRestClientProperties().socketTimeout)
+        return requestConfigBuilder.build()
+    }
+
+    open fun httpClient(): CloseableHttpClient {
+        return HttpClients.custom()
+            .addInterceptorFirst(WebClientUtils.logRequest())
+            .addInterceptorLast(WebClientUtils.logResponse())
+            .setDefaultRequestConfig(getRequestConfig())
+            .build()
+    }
+
+    override fun exchangeResource(methodType: String, path: String, request: String): BlueprintWebClientService.WebClientResponse<String> {
+        return this.exchangeResource(methodType, path, request, defaultHeaders())
+    }
+
+    override fun exchangeResource(
+        methodType: String,
+        path: String,
+        request: String,
+        headers: Map<String, String>
+    ): BlueprintWebClientService.WebClientResponse<String> {
+        /**
+         * TODO: Basic headers in the implementations of this client do not get added
+         * in blocking version, whereas in NB version defaultHeaders get added.
+         * the difference is in convertToBasicHeaders vs basicHeaders
+         */
+        val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
+        return when (HttpMethod.resolve(methodType)) {
+            HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
+            HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
+            HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
+            HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
+            HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
+            else -> throw BluePrintProcessorException(
+                "Unsupported methodType($methodType) attempted on path($path)"
+            )
+        }
+    }
+
+    // TODO: convert to multi-map
+    override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
+        return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
+    }
+
+    open fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
+        val httpDelete = HttpDelete(host(path))
+        RestLoggerService.httpInvoking(headers)
+        httpDelete.setHeaders(headers)
+        return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
+    }
+
+    open fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
+        val httpGet = HttpGet(host(path))
+        RestLoggerService.httpInvoking(headers)
+        httpGet.setHeaders(headers)
+        return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
+    }
+
+    open fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
+        val httpPost = HttpPost(host(path))
+        val entity = StringEntity(strRequest(request))
+        httpPost.entity = entity
+        RestLoggerService.httpInvoking(headers)
+        httpPost.setHeaders(headers)
+        return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
+    }
+
+    open fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
+        val httpPut = HttpPut(host(path))
+        val entity = StringEntity(strRequest(request))
+        httpPut.entity = entity
+        RestLoggerService.httpInvoking(headers)
+        httpPut.setHeaders(headers)
+        return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
+    }
+
+    open fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
+        val httpPatch = HttpPatch(host(path))
+        val entity = StringEntity(strRequest(request))
+        httpPatch.entity = entity
+        RestLoggerService.httpInvoking(headers)
+        httpPatch.setHeaders(headers)
+        return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
+    }
+
+    /**
+     * Perform the HTTP call and return HTTP status code and body.
+     * @param httpUriRequest {@link HttpUriRequest} object
+     * @return {@link WebClientResponse} object
+     * http client may throw IOException and ClientProtocolException on error
+     */
+
+    @Throws(IOException::class, ClientProtocolException::class)
+    protected fun <T> performCallAndExtractTypedWebClientResponse(
+        httpUriRequest: HttpUriRequest,
+        responseType: Class<T>
+    ):
+        BlueprintWebClientService.WebClientResponse<T> {
+            val httpResponse = httpClient().execute(httpUriRequest)
+            val statusCode = httpResponse.statusLine.statusCode
+            httpResponse.entity.content.use {
+                val body = getResponse(it, responseType)
+                return BlueprintWebClientService.WebClientResponse(statusCode, body)
+            }
+        }
+
+    open suspend fun getNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
+        return getNB(path, null, String::class.java)
+    }
+
+    open suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
+        return getNB(path, additionalHeaders, String::class.java)
+    }
+
+    open suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
+        BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
+            get(path, additionalHeaders!!, responseType)
+        }
+
+    open suspend fun postNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
+        return postNB(path, request, null, String::class.java)
+    }
+
+    open suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
+        return postNB(path, request, additionalHeaders, String::class.java)
+    }
+
+    open suspend fun <T> postNB(
+        path: String,
+        request: Any,
+        additionalHeaders: Array<BasicHeader>?,
+        responseType: Class<T>
+    ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
+        post(path, request, additionalHeaders!!, responseType)
+    }
+
+    open suspend fun putNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
+        return putNB(path, request, null, String::class.java)
+    }
+
+    open suspend fun putNB(
+        path: String,
+        request: Any,
+        additionalHeaders: Array<BasicHeader>?
+    ): BlueprintWebClientService.WebClientResponse<String> {
+        return putNB(path, request, additionalHeaders, String::class.java)
+    }
+
+    open suspend fun <T> putNB(
+        path: String,
+        request: Any,
+        additionalHeaders: Array<BasicHeader>?,
+        responseType: Class<T>
+    ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
+        put(path, request, additionalHeaders!!, responseType)
+    }
+
+    open suspend fun <T> deleteNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
+        return deleteNB(path, null, String::class.java)
+    }
+
+    open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
+        BlueprintWebClientService.WebClientResponse<String> {
+            return deleteNB(path, additionalHeaders, String::class.java)
+        }
+
+    open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
+        BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
+            delete(path, additionalHeaders!!, responseType)
+        }
+
+    open suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
+        BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
+            patch(path, request, additionalHeaders!!, responseType)
+        }
+
+    override suspend fun exchangeNB(methodType: String, path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
+        return exchangeNB(
+            methodType, path, request, hashMapOf(),
+            String::class.java
+        )
+    }
+
+    override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
+        BlueprintWebClientService.WebClientResponse<String> {
+            return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
+        }
+
+    override suspend fun <T> exchangeNB(
+        methodType: String,
+        path: String,
+        request: Any,
+        additionalHeaders: Map<String, String>?,
+        responseType: Class<T>
+    ): BlueprintWebClientService.WebClientResponse<T> {
+
+        // TODO: possible inconsistency
+        // NOTE: this basic headers function is different from non-blocking
+        val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
+        return when (HttpMethod.resolve(methodType)) {
+            HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
+            HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
+            HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
+            HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
+            HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
+            else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
+        }
+    }
+
+    protected fun strRequest(request: Any): String {
+        return when (request) {
+            is String -> request.toString()
+            is JsonNode -> request.toString()
+            else -> JacksonUtils.getJson(request)
+        }
+    }
+
+    protected fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
+        return if (responseType == String::class.java) {
+            IOUtils.toString(it, Charset.defaultCharset()) as T
+        } else {
+            JacksonUtils.readValue(it, responseType)!!
+        }
+    }
+
+    protected fun basicHeaders(headers: Map<String, String>?):
+        Array<BasicHeader> {
+            val basicHeaders = mutableListOf<BasicHeader>()
+            defaultHeaders().forEach { (name, value) ->
+                basicHeaders.add(BasicHeader(name, value))
+            }
+            headers?.forEach { name, value ->
+                basicHeaders.add(BasicHeader(name, value))
+            }
+            return basicHeaders.toTypedArray()
+        }
+
+    // Non Blocking Rest Implementation
+    suspend fun httpClientNB(): CloseableHttpClient {
+        return httpClient()
+    }
+
+    open fun verifyAdditionalHeaders(): Map<String, String> {
+        return verifyAdditionalHeaders(getRestClientProperties())
+    }
+
+    open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
+        val customHeaders: MutableMap<String, String> = mutableMapOf()
+        // Extract additionalHeaders from the requestProperties and
+        // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
+        restClientProperties.additionalHeaders?.let {
+            if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
+                val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
+                    " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
+                    " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
+                WebClientUtils.log.error(errMsg)
+                throw BluePrintProcessorException(errMsg)
+            } else {
+                customHeaders.putAll(it)
+            }
+        }
+        return customHeaders
+    }
+}
index be9b849..943a355 100644 (file)
@@ -20,21 +20,24 @@ import org.apache.http.message.BasicHeader
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
 import org.springframework.http.HttpHeaders
 import org.springframework.http.MediaType
-import java.net.URI
 import java.nio.charset.Charset
 import java.util.Base64
 
-class BasicAuthRestClientService(
+open class BasicAuthRestClientService(
     private val restClientProperties:
         BasicAuthRestClientProperties
 ) :
-    BlueprintWebClientService {
+    BaseBlueprintWebClientService<BasicAuthRestClientProperties>() {
+
+    override fun getRestClientProperties(): BasicAuthRestClientProperties {
+        return restClientProperties
+    }
 
     override fun defaultHeaders(): Map<String, String> {
 
         val encodedCredentials = setBasicAuth(
-            restClientProperties.username,
-            restClientProperties.password
+            getRestClientProperties().username,
+            getRestClientProperties().password
         )
         return mapOf(
             HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
@@ -43,21 +46,16 @@ class BasicAuthRestClientService(
         )
     }
 
-    override fun host(uri: String): String {
-        val uri: URI = URI.create(restClientProperties.url + uri)
-        return uri.resolve(uri).toString()
-    }
-
     override fun convertToBasicHeaders(headers: Map<String, String>):
         Array<BasicHeader> {
             val customHeaders: MutableMap<String, String> = headers.toMutableMap()
             // inject additionalHeaders
-            customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
+            customHeaders.putAll(verifyAdditionalHeaders())
 
             if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
                 val encodedCredentials = setBasicAuth(
-                    restClientProperties.username,
-                    restClientProperties.password
+                    getRestClientProperties().username,
+                    getRestClientProperties().password
                 )
                 customHeaders[HttpHeaders.AUTHORIZATION] =
                     "Basic $encodedCredentials"
index 945d298..ed52e62 100644 (file)
@@ -2,6 +2,7 @@
  * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation
  * Modifications Copyright © 2018-2019 IBM.
  * Modifications Copyright © 2019 Huawei.
+ * Modifications Copyright © 2022 Deutsche Telekom AG.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
 
-import com.fasterxml.jackson.databind.JsonNode
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import org.apache.commons.io.IOUtils
-import org.apache.http.client.ClientProtocolException
-import org.apache.http.client.methods.HttpDelete
-import org.apache.http.client.methods.HttpGet
-import org.apache.http.client.methods.HttpPatch
-import org.apache.http.client.methods.HttpPost
-import org.apache.http.client.methods.HttpPut
-import org.apache.http.client.methods.HttpUriRequest
-import org.apache.http.entity.StringEntity
-import org.apache.http.impl.client.CloseableHttpClient
-import org.apache.http.impl.client.HttpClients
 import org.apache.http.message.BasicHeader
-import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
-import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
-import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
-import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException
 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintIOUtils
-import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
-import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpMethod
-import java.io.IOException
-import java.io.InputStream
-import java.nio.charset.Charset
 
 interface BlueprintWebClientService {
-
     fun defaultHeaders(): Map<String, String>
-
-    fun host(uri: String): String
-
-    fun httpClient(): CloseableHttpClient {
-        return HttpClients.custom()
-            .addInterceptorFirst(WebClientUtils.logRequest())
-            .addInterceptorLast(WebClientUtils.logResponse())
-            .build()
-    }
-
-    /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException
-     * exception then this will perform wait and retrigger accoring to times [times] with delay [delay]
-     */
-    suspend fun <T> retry(
-        times: Int = 1,
-        initialDelay: Long = 0,
-        delay: Long = 1000,
-        block: suspend (Int) -> T
-    ): T {
-        val exceptionBlock = { e: Exception ->
-            if (e !is BluePrintRetryException) {
-                throw e
-            }
-        }
-        return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock)
-    }
-
-    fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
-        return this.exchangeResource(methodType, path, request, defaultHeaders())
-    }
+    fun convertToBasicHeaders(
+        mergedDefaultAndSuppliedHeaders: Map<String, String>
+    ): Array<BasicHeader>
 
     fun exchangeResource(
         methodType: String,
         path: String,
         request: String,
         headers: Map<String, String>
-    ): WebClientResponse<String> {
-        /**
-         * TODO: Basic headers in the implementations of this client do not get added
-         * in blocking version, whereas in NB version defaultHeaders get added.
-         * the difference is in convertToBasicHeaders vs basicHeaders
-         */
-        val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
-        return when (HttpMethod.resolve(methodType)) {
-            HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
-            HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
-            HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
-            HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
-            HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
-            else -> throw BluePrintProcessorException(
-                "Unsupported methodType($methodType) attempted on path($path)"
-            )
-        }
-    }
-
-    // TODO: convert to multi-map
-    fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
-        return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
-    }
+    ): WebClientResponse<String>
 
-    fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
-        val httpDelete = HttpDelete(host(path))
-        RestLoggerService.httpInvoking(headers)
-        httpDelete.setHeaders(headers)
-        return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
-    }
-
-    fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
-        val httpGet = HttpGet(host(path))
-        RestLoggerService.httpInvoking(headers)
-        httpGet.setHeaders(headers)
-        return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
-    }
-
-    fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
-        val httpPost = HttpPost(host(path))
-        val entity = StringEntity(strRequest(request))
-        httpPost.entity = entity
-        RestLoggerService.httpInvoking(headers)
-        httpPost.setHeaders(headers)
-        return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
-    }
-
-    fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
-        val httpPut = HttpPut(host(path))
-        val entity = StringEntity(strRequest(request))
-        httpPut.entity = entity
-        RestLoggerService.httpInvoking(headers)
-        httpPut.setHeaders(headers)
-        return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
-    }
-
-    fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
-        val httpPatch = HttpPatch(host(path))
-        val entity = StringEntity(strRequest(request))
-        httpPatch.entity = entity
-        RestLoggerService.httpInvoking(headers)
-        httpPatch.setHeaders(headers)
-        return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
-    }
-
-    /**
-     * Perform the HTTP call and return HTTP status code and body.
-     * @param httpUriRequest {@link HttpUriRequest} object
-     * @return {@link WebClientResponse} object
-     * http client may throw IOException and ClientProtocolException on error
-     */
-
-    @Throws(IOException::class, ClientProtocolException::class)
-    private fun <T> performCallAndExtractTypedWebClientResponse(
-        httpUriRequest: HttpUriRequest,
-        responseType: Class<T>
-    ):
-        WebClientResponse<T> {
-            val httpResponse = httpClient().execute(httpUriRequest)
-            val statusCode = httpResponse.statusLine.statusCode
-            httpResponse.entity.content.use {
-                val body = getResponse(it, responseType)
-                return WebClientResponse(statusCode, body)
-            }
-        }
-
-    suspend fun getNB(path: String): WebClientResponse<String> {
-        return getNB(path, null, String::class.java)
-    }
-
-    suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
-        return getNB(path, additionalHeaders, String::class.java)
-    }
-
-    suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
-        WebClientResponse<T> = withContext(Dispatchers.IO) {
-            get(path, additionalHeaders!!, responseType)
-        }
-
-    suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
-        return postNB(path, request, null, String::class.java)
-    }
-
-    suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
-        return postNB(path, request, additionalHeaders, String::class.java)
-    }
-
-    suspend fun <T> postNB(
-        path: String,
-        request: Any,
-        additionalHeaders: Array<BasicHeader>?,
-        responseType: Class<T>
-    ): WebClientResponse<T> = withContext(Dispatchers.IO) {
-        post(path, request, additionalHeaders!!, responseType)
-    }
-
-    suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
-        return putNB(path, request, null, String::class.java)
-    }
-
-    suspend fun putNB(
+    fun exchangeResource(
+        methodType: String,
         path: String,
-        request: Any,
-        additionalHeaders: Array<BasicHeader>?
-    ): WebClientResponse<String> {
-        return putNB(path, request, additionalHeaders, String::class.java)
-    }
+        request: String
+    ): WebClientResponse<String>
 
-    suspend fun <T> putNB(
-        path: String,
-        request: Any,
-        additionalHeaders: Array<BasicHeader>?,
-        responseType: Class<T>
-    ): WebClientResponse<T> = withContext(Dispatchers.IO) {
-        put(path, request, additionalHeaders!!, responseType)
-    }
-
-    suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
-        return deleteNB(path, null, String::class.java)
-    }
-
-    suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
-        WebClientResponse<String> {
-            return deleteNB(path, additionalHeaders, String::class.java)
-        }
-
-    suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
-        WebClientResponse<T> = withContext(Dispatchers.IO) {
-            delete(path, additionalHeaders!!, responseType)
-        }
-
-    suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
-        WebClientResponse<T> = withContext(Dispatchers.IO) {
-            patch(path, request, additionalHeaders!!, responseType)
-        }
-
-    suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
-        return exchangeNB(
-            methodType, path, request, hashMapOf(),
-            String::class.java
-        )
-    }
+    suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String>
 
     suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
-        WebClientResponse<String> {
-            return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
-        }
+        WebClientResponse<String>
 
     suspend fun <T> exchangeNB(
         methodType: String,
@@ -259,75 +53,25 @@ interface BlueprintWebClientService {
         request: Any,
         additionalHeaders: Map<String, String>?,
         responseType: Class<T>
-    ): WebClientResponse<T> {
+    ): WebClientResponse<T>
 
-        // TODO: possible inconsistency
-        // NOTE: this basic headers function is different from non-blocking
-        val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
-        return when (HttpMethod.resolve(methodType)) {
-            HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
-            HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
-            HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
-            HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
-            HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
-            else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
-        }
-    }
-
-    private fun strRequest(request: Any): String {
-        return when (request) {
-            is String -> request.toString()
-            is JsonNode -> request.toString()
-            else -> JacksonUtils.getJson(request)
-        }
-    }
-
-    private fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
-        return if (responseType == String::class.java) {
-            IOUtils.toString(it, Charset.defaultCharset()) as T
-        } else {
-            JacksonUtils.readValue(it, responseType)!!
-        }
-    }
-
-    private fun basicHeaders(headers: Map<String, String>?):
-        Array<BasicHeader> {
-            val basicHeaders = mutableListOf<BasicHeader>()
-            defaultHeaders().forEach { (name, value) ->
-                basicHeaders.add(BasicHeader(name, value))
-            }
-            headers?.forEach { name, value ->
-                basicHeaders.add(BasicHeader(name, value))
+    /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException
+     * exception then this will perform wait and retrigger accoring to times [times] with delay [delay]
+     */
+    suspend fun <T> retry(
+        times: Int = 1,
+        initialDelay: Long = 0,
+        delay: Long = 1000,
+        block: suspend (Int) -> T
+    ): T {
+        val exceptionBlock = { e: Exception ->
+            if (e !is BluePrintRetryException) {
+                throw e
             }
-            return basicHeaders.toTypedArray()
         }
-
-    // Non Blocking Rest Implementation
-    suspend fun httpClientNB(): CloseableHttpClient {
-        return HttpClients.custom()
-            .addInterceptorFirst(WebClientUtils.logRequest())
-            .addInterceptorLast(WebClientUtils.logResponse())
-            .build()
+        return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock)
     }
 
     // TODO maybe there could be cases where we care about return headers?
     data class WebClientResponse<T>(val status: Int, val body: T)
-
-    fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
-        val customHeaders: MutableMap<String, String> = mutableMapOf()
-        // Extract additionalHeaders from the requestProperties and
-        // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
-        restClientProperties.additionalHeaders?.let {
-            if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
-                val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
-                    " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
-                    " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
-                WebClientUtils.log.error(errMsg)
-                throw BluePrintProcessorException(errMsg)
-            } else {
-                customHeaders.putAll(it)
-            }
-        }
-        return customHeaders
-    }
 }
index a8d79b6..602609b 100644 (file)
@@ -24,6 +24,7 @@ import org.apache.http.impl.client.HttpClients
 import org.apache.http.message.BasicHeader
 import org.apache.http.ssl.SSLContextBuilder
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
+import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLBasicAuthRestClientProperties
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLRestClientProperties
 import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLTokenAuthRestClientProperties
@@ -35,8 +36,8 @@ import java.io.FileInputStream
 import java.security.KeyStore
 import java.security.cert.X509Certificate
 
-class SSLRestClientService(private val restClientProperties: SSLRestClientProperties) :
-    BlueprintWebClientService {
+open class SSLRestClientService(private val restClientProperties: SSLRestClientProperties) :
+    BaseBlueprintWebClientService<SSLRestClientProperties>() {
 
     var auth: BlueprintWebClientService? = null
 
@@ -44,7 +45,11 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper
         auth = getAuthService()
     }
 
-    private fun getAuthService(): BlueprintWebClientService? {
+    override fun getRestClientProperties(): SSLRestClientProperties {
+        return restClientProperties
+    }
+
+    private fun getAuthService(): BaseBlueprintWebClientService<RestClientProperties>? {
         // type,url and additional headers don't get carried over to TokenAuthRestClientProperties from SSLTokenAuthRestClientProperties
         // set them in auth obj to be consistent. TODO: refactor
         return when (restClientProperties) {
@@ -81,10 +86,6 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper
         )
     }
 
-    override fun host(uri: String): String {
-        return restClientProperties.url + uri
-    }
-
     override fun httpClient(): CloseableHttpClient {
 
         val keystoreInstance = restClientProperties.keyStoreInstance
@@ -117,14 +118,10 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper
         return HttpClients.custom()
             .addInterceptorFirst(WebClientUtils.logRequest())
             .addInterceptorLast(WebClientUtils.logResponse())
+            .setDefaultRequestConfig(getRequestConfig())
             .setSSLSocketFactory(csf).build()
     }
 
-    // Non Blocking Rest Implementation
-    override suspend fun httpClientNB(): CloseableHttpClient {
-        return httpClient()
-    }
-
     override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
         val mergedDefaultAndSuppliedHeaders = defaultHeaders().plus(headers)
         // During the initialization, getAuthService() sets the auth variable.
index 600eedf..34e4a9e 100644 (file)
@@ -21,11 +21,15 @@ import org.onap.ccsdk.cds.blueprintsprocessor.rest.TokenAuthRestClientProperties
 import org.springframework.http.HttpHeaders
 import org.springframework.http.MediaType
 
-class TokenAuthRestClientService(
+open class TokenAuthRestClientService(
     private val restClientProperties:
         TokenAuthRestClientProperties
 ) :
-    BlueprintWebClientService {
+    BaseBlueprintWebClientService<TokenAuthRestClientProperties>() {
+
+    override fun getRestClientProperties(): TokenAuthRestClientProperties {
+        return restClientProperties
+    }
 
     override fun defaultHeaders(): Map<String, String> {
         return mapOf(
@@ -45,8 +49,4 @@ class TokenAuthRestClientService(
             }
             return super.convertToBasicHeaders(customHeaders)
         }
-
-    override fun host(uri: String): String {
-        return restClientProperties.url + uri
-    }
 }