Enabling Code Formatter
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / BlueprintWebClientService.kt
index 5d35186..9beacfc 100644 (file)
@@ -33,13 +33,20 @@ 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 org.springframework.web.util.UriUtils
 import java.io.IOException
 import java.io.InputStream
 import java.nio.charset.Charset
+import java.nio.charset.StandardCharsets
 
 interface BlueprintWebClientService {
 
@@ -54,41 +61,67 @@ interface BlueprintWebClientService {
             .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 exchangeResource(methodType: String, path: String, request: String,
-                         headers: Map<String, String>): WebClientResponse<String> {
+    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)
+        val encodedPath = UriUtils.encodeQuery(path, StandardCharsets.UTF_8.name())
         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 met" +
-                "hodType($methodType)")
+            HttpMethod.DELETE -> delete(encodedPath, convertedHeaders, String::class.java)
+            HttpMethod.GET -> get(encodedPath, convertedHeaders, String::class.java)
+            HttpMethod.POST -> post(encodedPath, request, convertedHeaders, String::class.java)
+            HttpMethod.PUT -> put(encodedPath, request, convertedHeaders, String::class.java)
+            HttpMethod.PATCH -> patch(encodedPath, request, convertedHeaders, String::class.java)
+            else -> throw BluePrintProcessorException(
+                "Unsupported methodType($methodType) attempted on path($encodedPath)"
+            )
         }
     }
 
+    // TODO: convert to multi-map
     fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
         return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
     }
 
     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)
     }
@@ -97,6 +130,7 @@ interface BlueprintWebClientService {
         val httpPost = HttpPost(host(path))
         val entity = StringEntity(strRequest(request))
         httpPost.entity = entity
+        RestLoggerService.httpInvoking(headers)
         httpPost.setHeaders(headers)
         return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
     }
@@ -105,6 +139,7 @@ interface BlueprintWebClientService {
         val httpPut = HttpPut(host(path))
         val entity = StringEntity(strRequest(request))
         httpPut.entity = entity
+        RestLoggerService.httpInvoking(headers)
         httpPut.setHeaders(headers)
         return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
     }
@@ -113,6 +148,7 @@ interface BlueprintWebClientService {
         val httpPatch = HttpPatch(host(path))
         val entity = StringEntity(strRequest(request))
         httpPatch.entity = entity
+        RestLoggerService.httpInvoking(headers)
         httpPatch.setHeaders(headers)
         return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
     }
@@ -126,15 +162,17 @@ interface BlueprintWebClientService {
 
     @Throws(IOException::class, ClientProtocolException::class)
     private fun <T> performCallAndExtractTypedWebClientResponse(
-        httpUriRequest: HttpUriRequest, responseType: Class<T>):
+        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)
+            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)
@@ -145,8 +183,7 @@ interface BlueprintWebClientService {
     }
 
     suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
-        WebClientResponse<T> =
-        withContext(Dispatchers.IO) {
+        WebClientResponse<T> = withContext(Dispatchers.IO) {
             get(path, additionalHeaders!!, responseType)
         }
 
@@ -158,27 +195,35 @@ interface BlueprintWebClientService {
         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 <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(path: String, request: Any,
-                      additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
+    suspend fun putNB(
+        path: String,
+        request: Any,
+        additionalHeaders: Array<BasicHeader>?
+    ): WebClientResponse<String> {
         return putNB(path, request, additionalHeaders, String::class.java)
     }
 
-    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> 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)
@@ -186,37 +231,41 @@ interface BlueprintWebClientService {
 
     suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
         WebClientResponse<String> {
-        return deleteNB(path, additionalHeaders, String::class.java)
-    }
+            return deleteNB(path, additionalHeaders, String::class.java)
+        }
 
     suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
-        WebClientResponse<T> =
-        withContext(Dispatchers.IO) {
+        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) {
+        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)
+        return exchangeNB(
+            methodType, path, request, hashMapOf(),
+            String::class.java
+        )
     }
 
     suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
         WebClientResponse<String> {
-        return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
-    }
+            return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
+        }
 
-    suspend fun <T> exchangeNB(methodType: String, path: String, request: Any,
-                               additionalHeaders: Map<String, String>?,
-                               responseType: Class<T>): WebClientResponse<T> {
+    suspend fun <T> exchangeNB(
+        methodType: String,
+        path: String,
+        request: Any,
+        additionalHeaders: Map<String, String>?,
+        responseType: Class<T>
+    ): WebClientResponse<T> {
 
-        //TODO: possible inconsistency
-        //NOTE: this basic headers function is different from non-blocking
+        // 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)
@@ -224,8 +273,7 @@ interface BlueprintWebClientService {
             HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
             HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
             HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
-            else -> throw BluePrintProcessorException("Unsupported method" +
-                "Type($methodType)")
+            else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
         }
     }
 
@@ -247,15 +295,15 @@ interface BlueprintWebClientService {
 
     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))
+            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()
         }
-        return basicHeaders.toTypedArray()
-    }
 
     // Non Blocking Rest Implementation
     suspend fun httpClientNB(): CloseableHttpClient {
@@ -265,6 +313,24 @@ interface BlueprintWebClientService {
             .build()
     }
 
-    //TODO maybe there could be cases where we care about return headers?
+    // 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
+    }
 }