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.entity.EntityBuilder
32 import org.apache.http.client.methods.HttpDelete
33 import org.apache.http.client.methods.HttpGet
34 import org.apache.http.client.methods.HttpPatch
35 import org.apache.http.client.methods.HttpPost
36 import org.apache.http.client.methods.HttpPut
37 import org.apache.http.client.methods.HttpUriRequest
38 import org.apache.http.entity.StringEntity
39 import org.apache.http.impl.client.CloseableHttpClient
40 import org.apache.http.impl.client.HttpClients
41 import org.apache.http.message.BasicHeader
42 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
43 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
44 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse
45 import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
46 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
47 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
48 import org.springframework.http.HttpHeaders
49 import org.springframework.http.HttpMethod
50 import java.io.IOException
51 import java.io.InputStream
53 import java.nio.charset.Charset
54 import java.nio.file.Files
55 import java.nio.file.Path
57 abstract class BaseBlueprintWebClientService<out E : RestClientProperties> : BlueprintWebClientService {
59 open fun host(uri: String): String {
60 val uri: URI = URI.create(getRestClientProperties().url + uri)
61 return uri.resolve(uri).toString()
64 abstract fun getRestClientProperties(): E
66 open fun getRequestConfig(): RequestConfig {
67 val requestConfigBuilder = RequestConfig.custom()
68 if (getRestClientProperties().connectionRequestTimeout > 0)
69 requestConfigBuilder.setConnectionRequestTimeout(getRestClientProperties().connectionRequestTimeout)
70 if (getRestClientProperties().connectTimeout > 0)
71 requestConfigBuilder.setConnectTimeout(getRestClientProperties().connectTimeout)
72 if (getRestClientProperties().socketTimeout > 0)
73 requestConfigBuilder.setSocketTimeout(getRestClientProperties().socketTimeout)
74 return requestConfigBuilder.build()
77 open fun httpClient(): CloseableHttpClient {
78 return HttpClients.custom()
79 .addInterceptorFirst(WebClientUtils.logRequest())
80 .addInterceptorLast(WebClientUtils.logResponse())
81 .setDefaultRequestConfig(getRequestConfig())
85 override fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
86 return this.exchangeResource(methodType, path, request, defaultHeaders())
89 override fun exchangeResource(
93 headers: Map<String, String>
94 ): WebClientResponse<String> {
96 * TODO: Basic headers in the implementations of this client do not get added
97 * in blocking version, whereas in NB version defaultHeaders get added.
98 * the difference is in convertToBasicHeaders vs basicHeaders
100 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
101 return when (HttpMethod.resolve(methodType)) {
102 HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
103 HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
104 HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
105 HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
106 HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
107 else -> throw BluePrintProcessorException(
108 "Unsupported methodType($methodType) attempted on path($path)"
113 @Throws(IOException::class, ClientProtocolException::class)
114 protected fun performHttpCall(httpUriRequest: HttpUriRequest): WebClientResponse<String> {
115 val httpResponse = httpClient().execute(httpUriRequest)
116 val statusCode = httpResponse.statusLine.statusCode
117 httpResponse.entity.content.use {
118 val body = IOUtils.toString(it, Charset.defaultCharset())
119 return WebClientResponse(statusCode, body)
123 open override fun uploadBinaryFile(path: String, filePath: Path): WebClientResponse<String> {
124 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
125 val httpPost = HttpPost(host(path))
126 val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
127 httpPost.setEntity(entity)
128 RestLoggerService.httpInvoking(convertedHeaders)
129 httpPost.setHeaders(convertedHeaders)
130 return performHttpCall(httpPost)
133 // TODO: convert to multi-map
134 override fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
135 return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
138 open fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
139 val httpDelete = HttpDelete(host(path))
140 RestLoggerService.httpInvoking(headers)
141 httpDelete.setHeaders(headers)
142 return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
145 open fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
146 val httpGet = HttpGet(host(path))
147 RestLoggerService.httpInvoking(headers)
148 httpGet.setHeaders(headers)
149 return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
152 open fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
153 val httpPost = HttpPost(host(path))
154 val entity = StringEntity(strRequest(request))
155 httpPost.entity = entity
156 RestLoggerService.httpInvoking(headers)
157 httpPost.setHeaders(headers)
158 return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
161 open fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
162 val httpPut = HttpPut(host(path))
163 val entity = StringEntity(strRequest(request))
164 httpPut.entity = entity
165 RestLoggerService.httpInvoking(headers)
166 httpPut.setHeaders(headers)
167 return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
170 open fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
171 val httpPatch = HttpPatch(host(path))
172 val entity = StringEntity(strRequest(request))
173 httpPatch.entity = entity
174 RestLoggerService.httpInvoking(headers)
175 httpPatch.setHeaders(headers)
176 return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
180 * Perform the HTTP call and return HTTP status code and body.
181 * @param httpUriRequest {@link HttpUriRequest} object
182 * @return {@link WebClientResponse} object
183 * http client may throw IOException and ClientProtocolException on error
186 @Throws(IOException::class, ClientProtocolException::class)
187 protected fun <T> performCallAndExtractTypedWebClientResponse(
188 httpUriRequest: HttpUriRequest,
189 responseType: Class<T>
191 WebClientResponse<T> {
192 val httpResponse = httpClient().execute(httpUriRequest)
193 val statusCode = httpResponse.statusLine.statusCode
194 val entity: HttpEntity? = httpResponse.entity
195 if (canResponseHaveBody(httpResponse)) {
196 entity!!.content.use {
197 val body = getResponse(it, responseType)
198 return WebClientResponse(statusCode, body)
201 val constructor = responseType.getConstructor()
202 val body = constructor.newInstance()
203 return WebClientResponse(statusCode, body)
206 fun canResponseHaveBody(response: HttpResponse): Boolean {
207 val status = response.statusLine.statusCode
208 return response.entity !== null &&
209 status != HttpStatus.SC_NO_CONTENT &&
210 status != HttpStatus.SC_NOT_MODIFIED &&
211 status != HttpStatus.SC_RESET_CONTENT
214 open suspend fun getNB(path: String): WebClientResponse<String> {
215 return getNB(path, null, String::class.java)
218 open suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
219 return getNB(path, additionalHeaders, String::class.java)
222 open suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
223 WebClientResponse<T> = withContext(Dispatchers.IO) {
224 get(path, additionalHeaders!!, responseType)
227 open suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
228 return postNB(path, request, null, String::class.java)
231 open suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
232 return postNB(path, request, additionalHeaders, String::class.java)
235 open suspend fun <T> postNB(
238 additionalHeaders: Array<BasicHeader>?,
239 responseType: Class<T>
240 ): WebClientResponse<T> = withContext(Dispatchers.IO) {
241 post(path, request, additionalHeaders!!, responseType)
244 open suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
245 return putNB(path, request, null, String::class.java)
248 open suspend fun putNB(
251 additionalHeaders: Array<BasicHeader>?
252 ): WebClientResponse<String> {
253 return putNB(path, request, additionalHeaders, String::class.java)
256 open suspend fun <T> putNB(
259 additionalHeaders: Array<BasicHeader>?,
260 responseType: Class<T>
261 ): WebClientResponse<T> = withContext(Dispatchers.IO) {
262 put(path, request, additionalHeaders!!, responseType)
265 open suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
266 return deleteNB(path, null, String::class.java)
269 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
270 WebClientResponse<String> {
271 return deleteNB(path, additionalHeaders, String::class.java)
274 open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
275 WebClientResponse<T> = withContext(Dispatchers.IO) {
276 delete(path, additionalHeaders!!, responseType)
279 open suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
280 WebClientResponse<T> = withContext(Dispatchers.IO) {
281 patch(path, request, additionalHeaders!!, responseType)
284 override suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
286 methodType, path, request, hashMapOf(),
291 override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
292 WebClientResponse<String> {
293 return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
296 override suspend fun <T> exchangeNB(
300 additionalHeaders: Map<String, String>?,
301 responseType: Class<T>
302 ): WebClientResponse<T> {
304 // TODO: possible inconsistency
305 // NOTE: this basic headers function is different from non-blocking
306 val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
307 return when (HttpMethod.resolve(methodType)) {
308 HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
309 HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
310 HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
311 HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
312 HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
313 else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
317 protected fun strRequest(request: Any): String {
318 return when (request) {
319 is String -> request.toString()
320 is JsonNode -> request.toString()
321 else -> JacksonUtils.getJson(request)
325 protected fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
326 return if (responseType == String::class.java) {
327 IOUtils.toString(it, Charset.defaultCharset()) as T
329 JacksonUtils.readValue(it, responseType)!!
333 protected fun basicHeaders(headers: Map<String, String>?):
335 val basicHeaders = mutableListOf<BasicHeader>()
336 defaultHeaders().forEach { (name, value) ->
337 basicHeaders.add(BasicHeader(name, value))
339 headers?.forEach { name, value ->
340 basicHeaders.add(BasicHeader(name, value))
342 return basicHeaders.toTypedArray()
345 // Non Blocking Rest Implementation
346 suspend fun httpClientNB(): CloseableHttpClient {
350 open fun verifyAdditionalHeaders(): Map<String, String> {
351 return verifyAdditionalHeaders(getRestClientProperties())
354 open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
355 val customHeaders: MutableMap<String, String> = mutableMapOf()
356 // Extract additionalHeaders from the requestProperties and
357 // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
358 restClientProperties.additionalHeaders?.let {
359 if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
360 val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
361 " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
362 " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
363 WebClientUtils.log.error(errMsg)
364 throw BluePrintProcessorException(errMsg)
366 customHeaders.putAll(it)