Merge "BlueprintWebClientService: added return status"
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / BlueprintWebClientService.kt
1 /*
2  * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation
3  * Modifications Copyright © 2018-2019 IBM.
4  * Modifications Copyright © 2019 Huawei.
5  *
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
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
20
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
43
44 interface BlueprintWebClientService {
45
46     fun defaultHeaders(): Map<String, String>
47
48     fun host(uri: String): String
49
50     fun httpClient(): CloseableHttpClient {
51         return HttpClients.custom()
52             .addInterceptorFirst(WebClientUtils.logRequest())
53             .addInterceptorLast(WebClientUtils.logResponse())
54             .build()
55     }
56
57     fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
58         return this.exchangeResource(methodType, path, request, defaultHeaders())
59     }
60
61     fun exchangeResource(methodType: String, path: String, request: String,
62                          headers: Map<String, String>): WebClientResponse<String> {
63         /**
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
67          */
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)")
77         }
78     }
79
80     fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
81         return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
82     }
83
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)
88     }
89
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)
94     }
95
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)
102     }
103
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)
110     }
111
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)
118     }
119
120     /**
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
125      */
126
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)
136         }
137     }
138
139     suspend fun getNB(path: String): WebClientResponse<String> {
140         return getNB(path, null, String::class.java)
141     }
142
143     suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
144         return getNB(path, additionalHeaders, String::class.java)
145     }
146
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)
151         }
152
153     suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
154         return postNB(path, request, null, String::class.java)
155     }
156
157     suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
158         return postNB(path, request, additionalHeaders, String::class.java)
159     }
160
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)
165         }
166
167     suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
168         return putNB(path, request, null, String::class.java)
169     }
170
171     suspend fun putNB(path: String, request: Any,
172                       additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
173         return putNB(path, request, additionalHeaders, String::class.java)
174     }
175
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)
181         }
182
183     suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
184         return deleteNB(path, null, String::class.java)
185     }
186
187     suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
188         WebClientResponse<String> {
189         return deleteNB(path, additionalHeaders, String::class.java)
190     }
191
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)
196         }
197
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)
202         }
203
204     suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
205         return exchangeNB(methodType, path, request, hashMapOf(),
206             String::class.java)
207     }
208
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)
212     }
213
214     suspend fun <T> exchangeNB(methodType: String, path: String, request: Any,
215                                additionalHeaders: Map<String, String>?,
216                                responseType: Class<T>): WebClientResponse<T> {
217
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" +
228                 "Type($methodType)")
229         }
230     }
231
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)
237         }
238     }
239
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
243         } else {
244             JacksonUtils.readValue(it, responseType)!!
245         }
246     }
247
248     private fun basicHeaders(headers: Map<String, String>?):
249         Array<BasicHeader> {
250         val basicHeaders = mutableListOf<BasicHeader>()
251         defaultHeaders().forEach { (name, value) ->
252             basicHeaders.add(BasicHeader(name, value))
253         }
254         headers?.forEach { name, value ->
255             basicHeaders.add(BasicHeader(name, value))
256         }
257         return basicHeaders.toTypedArray()
258     }
259
260     // Non Blocking Rest Implementation
261     suspend fun httpClientNB(): CloseableHttpClient {
262         return HttpClients.custom()
263             .addInterceptorFirst(WebClientUtils.logRequest())
264             .addInterceptorLast(WebClientUtils.logResponse())
265             .build()
266     }
267
268     //TODO maybe there could be cases where we care about return headers?
269     data class WebClientResponse<T>(val status: Int, val body: T)
270 }