2 * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation
3 * Modifications Copyright © 2018-2019 IBM.
4 * Modifications Copyright © 2019 Huawei.
5 * Modifications Copyright © 2022 Deutsche Telekom AG.
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
22 import com.fasterxml.jackson.databind.JsonNode
23 import kotlinx.coroutines.Dispatchers
24 import kotlinx.coroutines.withContext
25 import org.apache.commons.io.IOUtils
26 import org.apache.http.client.ClientProtocolException
27 import org.apache.http.client.config.RequestConfig
28 import org.apache.http.client.methods.HttpDelete
29 import org.apache.http.client.methods.HttpGet
30 import org.apache.http.client.methods.HttpPatch
31 import org.apache.http.client.methods.HttpPost
32 import org.apache.http.client.methods.HttpPut
33 import org.apache.http.client.methods.HttpUriRequest
34 import org.apache.http.entity.StringEntity
35 import org.apache.http.impl.client.CloseableHttpClient
36 import org.apache.http.impl.client.HttpClients
37 import org.apache.http.message.BasicHeader
38 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
39 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
40 import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
41 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
42 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
43 import org.springframework.http.HttpHeaders
44 import org.springframework.http.HttpMethod
45 import java.io.IOException
46 import java.io.InputStream
48 import java.nio.charset.Charset
50 abstract class BaseBlueprintWebClientService<out E : RestClientProperties> : BlueprintWebClientService {
52 open fun host(uri: String): String {
53 val uri: URI = URI.create(getRestClientProperties().url + uri)
54 return uri.resolve(uri).toString()
57 abstract fun getRestClientProperties(): E
59 open fun getRequestConfig(): RequestConfig {
60 val requestConfigBuilder = RequestConfig.custom()
61 if (getRestClientProperties().connectionRequestTimeout > 0)
62 requestConfigBuilder.setConnectionRequestTimeout(getRestClientProperties().connectionRequestTimeout)
63 if (getRestClientProperties().connectTimeout > 0)
64 requestConfigBuilder.setConnectTimeout(getRestClientProperties().connectTimeout)
65 if (getRestClientProperties().socketTimeout > 0)
66 requestConfigBuilder.setSocketTimeout(getRestClientProperties().socketTimeout)
67 return requestConfigBuilder.build()
70 open fun httpClient(): CloseableHttpClient {
71 return HttpClients.custom()
72 .addInterceptorFirst(WebClientUtils.logRequest())
73 .addInterceptorLast(WebClientUtils.logResponse())
74 .setDefaultRequestConfig(getRequestConfig())
78 override fun exchangeResource(methodType: String, path: String, request: String): BlueprintWebClientService.WebClientResponse<String> {
79 return this.exchangeResource(methodType, path, request, defaultHeaders())
82 override fun exchangeResource(
86 headers: Map<String, String>
87 ): BlueprintWebClientService.WebClientResponse<String> {
89 * TODO: Basic headers in the implementations of this client do not get added
90 * in blocking version, whereas in NB version defaultHeaders get added.
91 * the difference is in convertToBasicHeaders vs basicHeaders
93 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
94 return when (HttpMethod.resolve(methodType)) {
95 HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
96 HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
97 HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
98 HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
99 HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
100 else -> throw BluePrintProcessorException(
101 "Unsupported methodType($methodType) attempted on path($path)"
106 // TODO: convert to multi-map
107 override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
108 return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
111 open fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
112 val httpDelete = HttpDelete(host(path))
113 RestLoggerService.httpInvoking(headers)
114 httpDelete.setHeaders(headers)
115 return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
118 open fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
119 val httpGet = HttpGet(host(path))
120 RestLoggerService.httpInvoking(headers)
121 httpGet.setHeaders(headers)
122 return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
125 open fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
126 val httpPost = HttpPost(host(path))
127 val entity = StringEntity(strRequest(request))
128 httpPost.entity = entity
129 RestLoggerService.httpInvoking(headers)
130 httpPost.setHeaders(headers)
131 return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
134 open fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
135 val httpPut = HttpPut(host(path))
136 val entity = StringEntity(strRequest(request))
137 httpPut.entity = entity
138 RestLoggerService.httpInvoking(headers)
139 httpPut.setHeaders(headers)
140 return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
143 open fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
144 val httpPatch = HttpPatch(host(path))
145 val entity = StringEntity(strRequest(request))
146 httpPatch.entity = entity
147 RestLoggerService.httpInvoking(headers)
148 httpPatch.setHeaders(headers)
149 return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
153 * Perform the HTTP call and return HTTP status code and body.
154 * @param httpUriRequest {@link HttpUriRequest} object
155 * @return {@link WebClientResponse} object
156 * http client may throw IOException and ClientProtocolException on error
159 @Throws(IOException::class, ClientProtocolException::class)
160 protected fun <T> performCallAndExtractTypedWebClientResponse(
161 httpUriRequest: HttpUriRequest,
162 responseType: Class<T>
164 BlueprintWebClientService.WebClientResponse<T> {
165 val httpResponse = httpClient().execute(httpUriRequest)
166 val statusCode = httpResponse.statusLine.statusCode
167 httpResponse.entity.content.use {
168 val body = getResponse(it, responseType)
169 return BlueprintWebClientService.WebClientResponse(statusCode, body)
173 open suspend fun getNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
174 return getNB(path, null, String::class.java)
177 open suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
178 return getNB(path, additionalHeaders, String::class.java)
181 open suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
182 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
183 get(path, additionalHeaders!!, responseType)
186 open suspend fun postNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
187 return postNB(path, request, null, String::class.java)
190 open suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
191 return postNB(path, request, additionalHeaders, String::class.java)
194 open suspend fun <T> postNB(
197 additionalHeaders: Array<BasicHeader>?,
198 responseType: Class<T>
199 ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
200 post(path, request, additionalHeaders!!, responseType)
203 open suspend fun putNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
204 return putNB(path, request, null, String::class.java)
207 open suspend fun putNB(
210 additionalHeaders: Array<BasicHeader>?
211 ): BlueprintWebClientService.WebClientResponse<String> {
212 return putNB(path, request, additionalHeaders, String::class.java)
215 open suspend fun <T> putNB(
218 additionalHeaders: Array<BasicHeader>?,
219 responseType: Class<T>
220 ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
221 put(path, request, additionalHeaders!!, responseType)
224 open suspend fun <T> deleteNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
225 return deleteNB(path, null, String::class.java)
228 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
229 BlueprintWebClientService.WebClientResponse<String> {
230 return deleteNB(path, additionalHeaders, String::class.java)
233 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
234 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
235 delete(path, additionalHeaders!!, responseType)
238 open suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
239 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
240 patch(path, request, additionalHeaders!!, responseType)
243 override suspend fun exchangeNB(methodType: String, path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
245 methodType, path, request, hashMapOf(),
250 override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
251 BlueprintWebClientService.WebClientResponse<String> {
252 return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
255 override suspend fun <T> exchangeNB(
259 additionalHeaders: Map<String, String>?,
260 responseType: Class<T>
261 ): BlueprintWebClientService.WebClientResponse<T> {
263 // TODO: possible inconsistency
264 // NOTE: this basic headers function is different from non-blocking
265 val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
266 return when (HttpMethod.resolve(methodType)) {
267 HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
268 HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
269 HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
270 HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
271 HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
272 else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
276 protected fun strRequest(request: Any): String {
277 return when (request) {
278 is String -> request.toString()
279 is JsonNode -> request.toString()
280 else -> JacksonUtils.getJson(request)
284 protected fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
285 return if (responseType == String::class.java) {
286 IOUtils.toString(it, Charset.defaultCharset()) as T
288 JacksonUtils.readValue(it, responseType)!!
292 protected fun basicHeaders(headers: Map<String, String>?):
294 val basicHeaders = mutableListOf<BasicHeader>()
295 defaultHeaders().forEach { (name, value) ->
296 basicHeaders.add(BasicHeader(name, value))
298 headers?.forEach { name, value ->
299 basicHeaders.add(BasicHeader(name, value))
301 return basicHeaders.toTypedArray()
304 // Non Blocking Rest Implementation
305 suspend fun httpClientNB(): CloseableHttpClient {
309 open fun verifyAdditionalHeaders(): Map<String, String> {
310 return verifyAdditionalHeaders(getRestClientProperties())
313 open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
314 val customHeaders: MutableMap<String, String> = mutableMapOf()
315 // Extract additionalHeaders from the requestProperties and
316 // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
317 restClientProperties.additionalHeaders?.let {
318 if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
319 val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
320 " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
321 " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
322 WebClientUtils.log.error(errMsg)
323 throw BluePrintProcessorException(errMsg)
325 customHeaders.putAll(it)