2 * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation
3 * Modifications Copyright © 2018-2019 IBM.
4 * Modifications Copyright © 2019 Huawei.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
21 import com.fasterxml.jackson.databind.JsonNode
22 import kotlinx.coroutines.Dispatchers
23 import kotlinx.coroutines.withContext
24 import org.apache.commons.io.IOUtils
25 import org.apache.http.client.ClientProtocolException
26 import org.apache.http.client.methods.*
27 import org.apache.http.entity.StringEntity
28 import org.apache.http.impl.client.CloseableHttpClient
29 import org.apache.http.impl.client.HttpClients
30 import org.apache.http.message.BasicHeader
31 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
32 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
33 import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
34 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
35 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException
36 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintIOUtils
37 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
38 import org.springframework.http.HttpHeaders
39 import org.springframework.http.HttpMethod
40 import java.io.IOException
41 import java.io.InputStream
42 import java.nio.charset.Charset
44 interface BlueprintWebClientService {
46 fun defaultHeaders(): Map<String, String>
48 fun host(uri: String): String
50 fun httpClient(): CloseableHttpClient {
51 return HttpClients.custom()
52 .addInterceptorFirst(WebClientUtils.logRequest())
53 .addInterceptorLast(WebClientUtils.logResponse())
57 /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException
58 * exception then this will perform wait and retrigger accoring to times [times] with delay [delay]
60 suspend fun <T> retry(times: Int = 1, initialDelay: Long = 0, delay: Long = 1000,
61 block: suspend (Int) -> T): T {
62 val exceptionBlock = { e: Exception ->
63 if (e !is BluePrintRetryException) {
67 return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock)
70 fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
71 return this.exchangeResource(methodType, path, request, defaultHeaders())
74 fun exchangeResource(methodType: String, path: String, request: String,
75 headers: Map<String, String>): WebClientResponse<String> {
77 * TODO: Basic headers in the implementations of this client do not get added
78 * in blocking version, whereas in NB version defaultHeaders get added.
79 * the difference is in convertToBasicHeaders vs basicHeaders
81 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
82 return when (HttpMethod.resolve(methodType)) {
83 HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
84 HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
85 HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
86 HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
87 HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
88 else -> throw BluePrintProcessorException(
89 "Unsupported methodType($methodType) attempted on path($path)")
93 //TODO: convert to multi-map
94 fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
95 return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
98 fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
99 val httpDelete = HttpDelete(host(path))
100 httpDelete.setHeaders(headers)
101 return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
104 fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
105 val httpGet = HttpGet(host(path))
106 httpGet.setHeaders(headers)
107 return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
110 fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
111 val httpPost = HttpPost(host(path))
112 val entity = StringEntity(strRequest(request))
113 httpPost.entity = entity
114 httpPost.setHeaders(headers)
115 return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
118 fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
119 val httpPut = HttpPut(host(path))
120 val entity = StringEntity(strRequest(request))
121 httpPut.entity = entity
122 httpPut.setHeaders(headers)
123 return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
126 fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
127 val httpPatch = HttpPatch(host(path))
128 val entity = StringEntity(strRequest(request))
129 httpPatch.entity = entity
130 httpPatch.setHeaders(headers)
131 return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
135 * Perform the HTTP call and return HTTP status code and body.
136 * @param httpUriRequest {@link HttpUriRequest} object
137 * @return {@link WebClientResponse} object
138 * http client may throw IOException and ClientProtocolException on error
141 @Throws(IOException::class, ClientProtocolException::class)
142 private fun <T> performCallAndExtractTypedWebClientResponse(
143 httpUriRequest: HttpUriRequest, responseType: Class<T>):
144 WebClientResponse<T> {
145 val httpResponse = httpClient().execute(httpUriRequest)
146 val statusCode = httpResponse.statusLine.statusCode
147 httpResponse.entity.content.use {
148 val body = getResponse(it, responseType)
149 return WebClientResponse(statusCode, body)
153 suspend fun getNB(path: String): WebClientResponse<String> {
154 return getNB(path, null, String::class.java)
157 suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
158 return getNB(path, additionalHeaders, String::class.java)
161 suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
162 WebClientResponse<T> = withContext(Dispatchers.IO) {
163 get(path, additionalHeaders!!, responseType)
166 suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
167 return postNB(path, request, null, String::class.java)
170 suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
171 return postNB(path, request, additionalHeaders, String::class.java)
174 suspend fun <T> postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?,
175 responseType: Class<T>): WebClientResponse<T> = withContext(Dispatchers.IO) {
176 post(path, request, additionalHeaders!!, responseType)
179 suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
180 return putNB(path, request, null, String::class.java)
183 suspend fun putNB(path: String, request: Any,
184 additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
185 return putNB(path, request, additionalHeaders, String::class.java)
188 suspend fun <T> putNB(path: String, request: Any,
189 additionalHeaders: Array<BasicHeader>?,
190 responseType: Class<T>): WebClientResponse<T> = withContext(Dispatchers.IO) {
191 put(path, request, additionalHeaders!!, responseType)
194 suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
195 return deleteNB(path, null, String::class.java)
198 suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
199 WebClientResponse<String> {
200 return deleteNB(path, additionalHeaders, String::class.java)
203 suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
204 WebClientResponse<T> = withContext(Dispatchers.IO) {
205 delete(path, additionalHeaders!!, responseType)
208 suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
209 WebClientResponse<T> = withContext(Dispatchers.IO) {
210 patch(path, request, additionalHeaders!!, responseType)
213 suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
214 return exchangeNB(methodType, path, request, hashMapOf(),
218 suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
219 WebClientResponse<String> {
220 return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
223 suspend fun <T> exchangeNB(methodType: String, path: String, request: Any,
224 additionalHeaders: Map<String, String>?,
225 responseType: Class<T>): WebClientResponse<T> {
227 //TODO: possible inconsistency
228 //NOTE: this basic headers function is different from non-blocking
229 val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
230 return when (HttpMethod.resolve(methodType)) {
231 HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
232 HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
233 HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
234 HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
235 HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
236 else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
240 private fun strRequest(request: Any): String {
241 return when (request) {
242 is String -> request.toString()
243 is JsonNode -> request.toString()
244 else -> JacksonUtils.getJson(request)
248 private fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
249 return if (responseType == String::class.java) {
250 IOUtils.toString(it, Charset.defaultCharset()) as T
252 JacksonUtils.readValue(it, responseType)!!
256 private fun basicHeaders(headers: Map<String, String>?):
258 val basicHeaders = mutableListOf<BasicHeader>()
259 defaultHeaders().forEach { (name, value) ->
260 basicHeaders.add(BasicHeader(name, value))
262 headers?.forEach { name, value ->
263 basicHeaders.add(BasicHeader(name, value))
265 return basicHeaders.toTypedArray()
268 // Non Blocking Rest Implementation
269 suspend fun httpClientNB(): CloseableHttpClient {
270 return HttpClients.custom()
271 .addInterceptorFirst(WebClientUtils.logRequest())
272 .addInterceptorLast(WebClientUtils.logResponse())
276 //TODO maybe there could be cases where we care about return headers?
277 data class WebClientResponse<T>(val status: Int, val body: T)
279 fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> {
280 val customHeaders: MutableMap<String, String> = mutableMapOf()
281 //Extract additionalHeaders from the requestProperties and
282 //throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive)
283 restClientProperties.additionalHeaders?.let {
284 if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) {
285 val errMsg = "Error in definition of endpoint ${restClientProperties.url}." +
286 " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" +
287 " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\""
288 WebClientUtils.log.error(errMsg)
289 throw BluePrintProcessorException(errMsg)
291 customHeaders.putAll(it)