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 {
.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)
}
val httpPost = HttpPost(host(path))
val entity = StringEntity(strRequest(request))
httpPost.entity = entity
+ RestLoggerService.httpInvoking(headers)
httpPost.setHeaders(headers)
return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
}
val httpPut = HttpPut(host(path))
val entity = StringEntity(strRequest(request))
httpPut.entity = entity
+ RestLoggerService.httpInvoking(headers)
httpPut.setHeaders(headers)
return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
}
val httpPatch = HttpPatch(host(path))
val entity = StringEntity(strRequest(request))
httpPatch.entity = entity
+ RestLoggerService.httpInvoking(headers)
httpPatch.setHeaders(headers)
return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
}
@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)
}
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)
}
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)
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)
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)")
}
}
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 {
.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
+ }
}