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.HttpDelete
27 import org.apache.http.client.methods.HttpGet
28 import org.apache.http.client.methods.HttpPatch
29 import org.apache.http.client.methods.HttpPost
30 import org.apache.http.client.methods.HttpPut
31 import org.apache.http.client.methods.HttpUriRequest
32 import org.apache.http.entity.StringEntity
33 import org.apache.http.impl.client.CloseableHttpClient
34 import org.apache.http.impl.client.HttpClients
35 import org.apache.http.message.BasicHeader
36 import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils
37 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
38 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
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 fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
58 return this.exchangeResource(methodType, path, request, defaultHeaders())
61 fun exchangeResource(methodType: String, path: String, request: String,
62 headers: Map<String, String>): WebClientResponse<String> {
64 * TODO: Basic headers in the implementations of this client do not get added
65 * in blocking version, whereas in NB version defaultHeaders get added.
66 * the difference is in convertToBasicHeaders vs basicHeaders
68 val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
69 return when (HttpMethod.resolve(methodType)) {
70 HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
71 HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
72 HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
73 HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
74 HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
75 else -> throw BluePrintProcessorException("Unsupported met" +
76 "hodType($methodType)")
80 fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
81 return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
84 fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
85 val httpDelete = HttpDelete(host(path))
86 httpDelete.setHeaders(headers)
87 return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
90 fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
91 val httpGet = HttpGet(host(path))
92 httpGet.setHeaders(headers)
93 return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
96 fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
97 val httpPost = HttpPost(host(path))
98 val entity = StringEntity(strRequest(request))
99 httpPost.entity = entity
100 httpPost.setHeaders(headers)
101 return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
104 fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
105 val httpPut = HttpPut(host(path))
106 val entity = StringEntity(strRequest(request))
107 httpPut.entity = entity
108 httpPut.setHeaders(headers)
109 return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
112 fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
113 val httpPatch = HttpPatch(host(path))
114 val entity = StringEntity(strRequest(request))
115 httpPatch.entity = entity
116 httpPatch.setHeaders(headers)
117 return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
121 * Perform the HTTP call and return HTTP status code and body.
122 * @param httpUriRequest {@link HttpUriRequest} object
123 * @return {@link WebClientResponse} object
124 * http client may throw IOException and ClientProtocolException on error
127 @Throws(IOException::class, ClientProtocolException::class)
128 private fun <T> performCallAndExtractTypedWebClientResponse(
129 httpUriRequest: HttpUriRequest, responseType: Class<T>):
130 WebClientResponse<T> {
131 val httpResponse = httpClient().execute(httpUriRequest)
132 val statusCode = httpResponse.statusLine.statusCode
133 httpResponse.entity.content.use {
134 val body = getResponse(it, responseType)
135 return WebClientResponse(statusCode, body)
139 suspend fun getNB(path: String): WebClientResponse<String> {
140 return getNB(path, null, String::class.java)
143 suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
144 return getNB(path, additionalHeaders, String::class.java)
147 suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
148 WebClientResponse<T> =
149 withContext(Dispatchers.IO) {
150 get(path, additionalHeaders!!, responseType)
153 suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
154 return postNB(path, request, null, String::class.java)
157 suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
158 return postNB(path, request, additionalHeaders, String::class.java)
161 suspend fun <T> postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?,
162 responseType: Class<T>): WebClientResponse<T> =
163 withContext(Dispatchers.IO) {
164 post(path, request, additionalHeaders!!, responseType)
167 suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
168 return putNB(path, request, null, String::class.java)
171 suspend fun putNB(path: String, request: Any,
172 additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
173 return putNB(path, request, additionalHeaders, String::class.java)
176 suspend fun <T> putNB(path: String, request: Any,
177 additionalHeaders: Array<BasicHeader>?,
178 responseType: Class<T>): WebClientResponse<T> =
179 withContext(Dispatchers.IO) {
180 put(path, request, additionalHeaders!!, responseType)
183 suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
184 return deleteNB(path, null, String::class.java)
187 suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
188 WebClientResponse<String> {
189 return deleteNB(path, additionalHeaders, String::class.java)
192 suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
193 WebClientResponse<T> =
194 withContext(Dispatchers.IO) {
195 delete(path, additionalHeaders!!, responseType)
198 suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
199 WebClientResponse<T> =
200 withContext(Dispatchers.IO) {
201 patch(path, request, additionalHeaders!!, responseType)
204 suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
205 return exchangeNB(methodType, path, request, hashMapOf(),
209 suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
210 WebClientResponse<String> {
211 return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
214 suspend fun <T> exchangeNB(methodType: String, path: String, request: Any,
215 additionalHeaders: Map<String, String>?,
216 responseType: Class<T>): WebClientResponse<T> {
218 //TODO: possible inconsistency
219 //NOTE: this basic headers function is different from non-blocking
220 val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
221 return when (HttpMethod.resolve(methodType)) {
222 HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
223 HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
224 HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
225 HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
226 HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
227 else -> throw BluePrintProcessorException("Unsupported method" +
232 private fun strRequest(request: Any): String {
233 return when (request) {
234 is String -> request.toString()
235 is JsonNode -> request.toString()
236 else -> JacksonUtils.getJson(request)
240 private fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
241 return if (responseType == String::class.java) {
242 IOUtils.toString(it, Charset.defaultCharset()) as T
244 JacksonUtils.readValue(it, responseType)!!
248 private fun basicHeaders(headers: Map<String, String>?):
250 val basicHeaders = mutableListOf<BasicHeader>()
251 defaultHeaders().forEach { (name, value) ->
252 basicHeaders.add(BasicHeader(name, value))
254 headers?.forEach { name, value ->
255 basicHeaders.add(BasicHeader(name, value))
257 return basicHeaders.toTypedArray()
260 // Non Blocking Rest Implementation
261 suspend fun httpClientNB(): CloseableHttpClient {
262 return HttpClients.custom()
263 .addInterceptorFirst(WebClientUtils.logRequest())
264 .addInterceptorLast(WebClientUtils.logResponse())
268 //TODO maybe there could be cases where we care about return headers?
269 data class WebClientResponse<T>(val status: Int, val body: T)