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.HttpEntity
27 import org.apache.http.HttpResponse
28 import org.apache.http.HttpStatus
29 import org.apache.http.client.ClientProtocolException
30 import org.apache.http.client.config.RequestConfig
31 import org.apache.http.client.methods.HttpDelete
32 import org.apache.http.client.methods.HttpGet
33 import org.apache.http.client.methods.HttpPatch
34 import org.apache.http.client.methods.HttpPost
35 import org.apache.http.client.methods.HttpPut
36 import org.apache.http.client.methods.HttpUriRequest
37 import org.apache.http.entity.StringEntity
38 import org.apache.http.impl.client.CloseableHttpClient
39 import org.apache.http.impl.client.HttpClients
40 import org.apache.http.message.BasicHeader
41 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
42 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
43 import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
44 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
45 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
46 import org.springframework.http.HttpHeaders
47 import org.springframework.http.HttpMethod
48 import java.io.IOException
49 import java.io.InputStream
51 import java.nio.charset.Charset
53 abstract class BaseBlueprintWebClientService<out E : RestClientProperties> : BlueprintWebClientService {
55 open fun host(uri: String): String {
56 val uri: URI = URI.create(getRestClientProperties().url + uri)
57 return uri.resolve(uri).toString()
60 abstract fun getRestClientProperties(): E
62 open fun getRequestConfig(): RequestConfig {
63 val requestConfigBuilder = RequestConfig.custom()
64 if (getRestClientProperties().connectionRequestTimeout > 0)
65 requestConfigBuilder.setConnectionRequestTimeout(getRestClientProperties().connectionRequestTimeout)
66 if (getRestClientProperties().connectTimeout > 0)
67 requestConfigBuilder.setConnectTimeout(getRestClientProperties().connectTimeout)
68 if (getRestClientProperties().socketTimeout > 0)
69 requestConfigBuilder.setSocketTimeout(getRestClientProperties().socketTimeout)
70 return requestConfigBuilder.build()
73 open fun httpClient(): CloseableHttpClient {
74 return HttpClients.custom()
75 .addInterceptorFirst(WebClientUtils.logRequest())
76 .addInterceptorLast(WebClientUtils.logResponse())
77 .setDefaultRequestConfig(getRequestConfig())
81 override fun exchangeResource(methodType: String, path: String, request: String): BlueprintWebClientService.WebClientResponse<String> {
82 return this.exchangeResource(methodType, path, request, defaultHeaders())
85 override fun exchangeResource(
89 headers: Map<String, String>
90 ): BlueprintWebClientService.WebClientResponse<String> {
92 * TODO: Basic headers in the implementations of this client do not get added
93 * in blocking version, whereas in NB version defaultHeaders get added.
94 * the difference is in convertToBasicHeaders vs basicHeaders
96 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
97 return when (HttpMethod.resolve(methodType)) {
98 HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
99 HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
100 HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
101 HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
102 HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
103 else -> throw BluePrintProcessorException(
104 "Unsupported methodType($methodType) attempted on path($path)"
109 // TODO: convert to multi-map
110 override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
111 return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
114 open fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
115 val httpDelete = HttpDelete(host(path))
116 RestLoggerService.httpInvoking(headers)
117 httpDelete.setHeaders(headers)
118 return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
121 open fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
122 val httpGet = HttpGet(host(path))
123 RestLoggerService.httpInvoking(headers)
124 httpGet.setHeaders(headers)
125 return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
128 open fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
129 val httpPost = HttpPost(host(path))
130 val entity = StringEntity(strRequest(request))
131 httpPost.entity = entity
132 RestLoggerService.httpInvoking(headers)
133 httpPost.setHeaders(headers)
134 return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
137 open fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
138 val httpPut = HttpPut(host(path))
139 val entity = StringEntity(strRequest(request))
140 httpPut.entity = entity
141 RestLoggerService.httpInvoking(headers)
142 httpPut.setHeaders(headers)
143 return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
146 open fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> {
147 val httpPatch = HttpPatch(host(path))
148 val entity = StringEntity(strRequest(request))
149 httpPatch.entity = entity
150 RestLoggerService.httpInvoking(headers)
151 httpPatch.setHeaders(headers)
152 return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
156 * Perform the HTTP call and return HTTP status code and body.
157 * @param httpUriRequest {@link HttpUriRequest} object
158 * @return {@link WebClientResponse} object
159 * http client may throw IOException and ClientProtocolException on error
162 @Throws(IOException::class, ClientProtocolException::class)
163 protected fun <T> performCallAndExtractTypedWebClientResponse(
164 httpUriRequest: HttpUriRequest,
165 responseType: Class<T>
167 BlueprintWebClientService.WebClientResponse<T> {
168 val httpResponse = httpClient().execute(httpUriRequest)
169 val statusCode = httpResponse.statusLine.statusCode
170 val entity: HttpEntity? = httpResponse.entity
171 if (canResponseHaveBody(httpResponse)) {
172 entity!!.content.use {
173 val body = getResponse(it, responseType)
174 return BlueprintWebClientService.WebClientResponse(statusCode, body)
177 val constructor = responseType.getConstructor()
178 val body = constructor.newInstance()
179 return BlueprintWebClientService.WebClientResponse(statusCode, body)
182 fun canResponseHaveBody(response: HttpResponse): Boolean {
183 val status = response.statusLine.statusCode
184 return response.entity !== null &&
185 status != HttpStatus.SC_NO_CONTENT &&
186 status != HttpStatus.SC_NOT_MODIFIED &&
187 status != HttpStatus.SC_RESET_CONTENT
190 open suspend fun getNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
191 return getNB(path, null, String::class.java)
194 open suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
195 return getNB(path, additionalHeaders, String::class.java)
198 open suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
199 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
200 get(path, additionalHeaders!!, responseType)
203 open suspend fun postNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
204 return postNB(path, request, null, String::class.java)
207 open suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> {
208 return postNB(path, request, additionalHeaders, String::class.java)
211 open suspend fun <T> postNB(
214 additionalHeaders: Array<BasicHeader>?,
215 responseType: Class<T>
216 ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
217 post(path, request, additionalHeaders!!, responseType)
220 open suspend fun putNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
221 return putNB(path, request, null, String::class.java)
224 open suspend fun putNB(
227 additionalHeaders: Array<BasicHeader>?
228 ): BlueprintWebClientService.WebClientResponse<String> {
229 return putNB(path, request, additionalHeaders, String::class.java)
232 open suspend fun <T> putNB(
235 additionalHeaders: Array<BasicHeader>?,
236 responseType: Class<T>
237 ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
238 put(path, request, additionalHeaders!!, responseType)
241 open suspend fun <T> deleteNB(path: String): BlueprintWebClientService.WebClientResponse<String> {
242 return deleteNB(path, null, String::class.java)
245 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
246 BlueprintWebClientService.WebClientResponse<String> {
247 return deleteNB(path, additionalHeaders, String::class.java)
250 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
251 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
252 delete(path, additionalHeaders!!, responseType)
255 open suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
256 BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) {
257 patch(path, request, additionalHeaders!!, responseType)
260 override suspend fun exchangeNB(methodType: String, path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> {
262 methodType, path, request, hashMapOf(),
267 override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
268 BlueprintWebClientService.WebClientResponse<String> {
269 return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
272 override suspend fun <T> exchangeNB(
276 additionalHeaders: Map<String, String>?,
277 responseType: Class<T>
278 ): BlueprintWebClientService.WebClientResponse<T> {
280 // TODO: possible inconsistency
281 // NOTE: this basic headers function is different from non-blocking
282 val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
283 return when (HttpMethod.resolve(methodType)) {
284 HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
285 HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
286 HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
287 HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
288 HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
289 else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
293 protected fun strRequest(request: Any): String {
294 return when (request) {
295 is String -> request.toString()
296 is JsonNode -> request.toString()
297 else -> JacksonUtils.getJson(request)
301 protected fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
302 return if (responseType == String::class.java) {
303 IOUtils.toString(it, Charset.defaultCharset()) as T
305 JacksonUtils.readValue(it, responseType)!!
309 protected fun basicHeaders(headers: Map<String, String>?):
311 val basicHeaders = mutableListOf<BasicHeader>()
312 defaultHeaders().forEach { (name, value) ->
313 basicHeaders.add(BasicHeader(name, value))
315 headers?.forEach { name, value ->
316 basicHeaders.add(BasicHeader(name, value))
318 return basicHeaders.toTypedArray()
321 // Non Blocking Rest Implementation
322 suspend fun httpClientNB(): CloseableHttpClient {
326 open fun verifyAdditionalHeaders(): Map<String, String> {
327 return verifyAdditionalHeaders(getRestClientProperties())
330 open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
331 val customHeaders: MutableMap<String, String> = mutableMapOf()
332 // Extract additionalHeaders from the requestProperties and
333 // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
334 restClientProperties.additionalHeaders?.let {
335 if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
336 val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
337 " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
338 " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
339 WebClientUtils.log.error(errMsg)
340 throw BluePrintProcessorException(errMsg)
342 customHeaders.putAll(it)