diff options
Diffstat (limited to 'ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt')
-rw-r--r-- | ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt | 330 |
1 files changed, 330 insertions, 0 deletions
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 index 000000000..bc1dc4e09 --- /dev/null +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt @@ -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 + } +} |