Merge "Clean restconf duplicate models and Implementation."
[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.*
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.utils.WebClientUtils
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
33 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException
34 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintIOUtils
35 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
36 import org.springframework.http.HttpMethod
37 import java.io.IOException
38 import java.io.InputStream
39 import java.nio.charset.Charset
40
41 interface BlueprintWebClientService {
42
43     fun defaultHeaders(): Map<String, String>
44
45     fun host(uri: String): String
46
47     fun httpClient(): CloseableHttpClient {
48         return HttpClients.custom()
49                 .addInterceptorFirst(WebClientUtils.logRequest())
50                 .addInterceptorLast(WebClientUtils.logResponse())
51                 .build()
52     }
53
54     /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException
55      * exception then this will perform wait and retrigger accoring to times [times] with delay [delay]
56      */
57     suspend fun <T> retry(times: Int = 1, initialDelay: Long = 0, delay: Long = 1000,
58                           block: suspend (Int) -> T): T {
59         val exceptionBlock = { e: Exception ->
60             if (e !is BluePrintRetryException) {
61                 throw e
62             }
63         }
64         return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock)
65     }
66
67     fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> {
68         return this.exchangeResource(methodType, path, request, defaultHeaders())
69     }
70
71     fun exchangeResource(methodType: String, path: String, request: String,
72                          headers: Map<String, String>): WebClientResponse<String> {
73         /**
74          * TODO: Basic headers in the implementations of this client do not get added
75          * in blocking version, whereas in NB version defaultHeaders get added.
76          * the difference is in convertToBasicHeaders vs basicHeaders
77          */
78         val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(headers)
79         return when (HttpMethod.resolve(methodType)) {
80             HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java)
81             HttpMethod.GET -> get(path, convertedHeaders, String::class.java)
82             HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java)
83             HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java)
84             HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java)
85             else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
86         }
87     }
88
89     fun convertToBasicHeaders(headers: Map<String, String>): Array<BasicHeader> {
90         return headers.map { BasicHeader(it.key, it.value) }.toTypedArray()
91     }
92
93     fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
94         val httpDelete = HttpDelete(host(path))
95         httpDelete.setHeaders(headers)
96         return performCallAndExtractTypedWebClientResponse(httpDelete, responseType)
97     }
98
99     fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
100         val httpGet = HttpGet(host(path))
101         httpGet.setHeaders(headers)
102         return performCallAndExtractTypedWebClientResponse(httpGet, responseType)
103     }
104
105     fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
106         val httpPost = HttpPost(host(path))
107         val entity = StringEntity(strRequest(request))
108         httpPost.entity = entity
109         httpPost.setHeaders(headers)
110         return performCallAndExtractTypedWebClientResponse(httpPost, responseType)
111     }
112
113     fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
114         val httpPut = HttpPut(host(path))
115         val entity = StringEntity(strRequest(request))
116         httpPut.entity = entity
117         httpPut.setHeaders(headers)
118         return performCallAndExtractTypedWebClientResponse(httpPut, responseType)
119     }
120
121     fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> {
122         val httpPatch = HttpPatch(host(path))
123         val entity = StringEntity(strRequest(request))
124         httpPatch.entity = entity
125         httpPatch.setHeaders(headers)
126         return performCallAndExtractTypedWebClientResponse(httpPatch, responseType)
127     }
128
129     /**
130      * Perform the HTTP call and return HTTP status code and body.
131      * @param httpUriRequest {@link HttpUriRequest} object
132      * @return {@link WebClientResponse} object
133      * http client may throw IOException and ClientProtocolException on error
134      */
135
136     @Throws(IOException::class, ClientProtocolException::class)
137     private fun <T> performCallAndExtractTypedWebClientResponse(
138             httpUriRequest: HttpUriRequest, responseType: Class<T>):
139             WebClientResponse<T> {
140         val httpResponse = httpClient().execute(httpUriRequest)
141         val statusCode = httpResponse.statusLine.statusCode
142         httpResponse.entity.content.use {
143             val body = getResponse(it, responseType)
144             return WebClientResponse(statusCode, body)
145         }
146     }
147
148     suspend fun getNB(path: String): WebClientResponse<String> {
149         return getNB(path, null, String::class.java)
150     }
151
152     suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
153         return getNB(path, additionalHeaders, String::class.java)
154     }
155
156     suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
157             WebClientResponse<T> = withContext(Dispatchers.IO) {
158         get(path, additionalHeaders!!, responseType)
159     }
160
161     suspend fun postNB(path: String, request: Any): WebClientResponse<String> {
162         return postNB(path, request, null, String::class.java)
163     }
164
165     suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
166         return postNB(path, request, additionalHeaders, String::class.java)
167     }
168
169     suspend fun <T> postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?,
170                            responseType: Class<T>): WebClientResponse<T> = withContext(Dispatchers.IO) {
171         post(path, request, additionalHeaders!!, responseType)
172     }
173
174     suspend fun putNB(path: String, request: Any): WebClientResponse<String> {
175         return putNB(path, request, null, String::class.java)
176     }
177
178     suspend fun putNB(path: String, request: Any,
179                       additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> {
180         return putNB(path, request, additionalHeaders, String::class.java)
181     }
182
183     suspend fun <T> putNB(path: String, request: Any,
184                           additionalHeaders: Array<BasicHeader>?,
185                           responseType: Class<T>): WebClientResponse<T> = withContext(Dispatchers.IO) {
186         put(path, request, additionalHeaders!!, responseType)
187     }
188
189     suspend fun <T> deleteNB(path: String): WebClientResponse<String> {
190         return deleteNB(path, null, String::class.java)
191     }
192
193     suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?):
194             WebClientResponse<String> {
195         return deleteNB(path, additionalHeaders, String::class.java)
196     }
197
198     suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
199             WebClientResponse<T> = withContext(Dispatchers.IO) {
200         delete(path, additionalHeaders!!, responseType)
201     }
202
203     suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>):
204             WebClientResponse<T> = withContext(Dispatchers.IO) {
205         patch(path, request, additionalHeaders!!, responseType)
206     }
207
208     suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> {
209         return exchangeNB(methodType, path, request, hashMapOf(),
210                 String::class.java)
211     }
212
213     suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?):
214             WebClientResponse<String> {
215         return exchangeNB(methodType, path, request, additionalHeaders, String::class.java)
216     }
217
218     suspend fun <T> exchangeNB(methodType: String, path: String, request: Any,
219                                additionalHeaders: Map<String, String>?,
220                                responseType: Class<T>): WebClientResponse<T> {
221
222         //TODO: possible inconsistency
223         //NOTE: this basic headers function is different from non-blocking
224         val convertedHeaders: Array<BasicHeader> = basicHeaders(additionalHeaders!!)
225         return when (HttpMethod.resolve(methodType)) {
226             HttpMethod.GET -> getNB(path, convertedHeaders, responseType)
227             HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType)
228             HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType)
229             HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType)
230             HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType)
231             else -> throw BluePrintProcessorException("Unsupported methodType($methodType)")
232         }
233     }
234
235     private fun strRequest(request: Any): String {
236         return when (request) {
237             is String -> request.toString()
238             is JsonNode -> request.toString()
239             else -> JacksonUtils.getJson(request)
240         }
241     }
242
243     private fun <T> getResponse(it: InputStream, responseType: Class<T>): T {
244         return if (responseType == String::class.java) {
245             IOUtils.toString(it, Charset.defaultCharset()) as T
246         } else {
247             JacksonUtils.readValue(it, responseType)!!
248         }
249     }
250
251     private fun basicHeaders(headers: Map<String, String>?):
252             Array<BasicHeader> {
253         val basicHeaders = mutableListOf<BasicHeader>()
254         defaultHeaders().forEach { (name, value) ->
255             basicHeaders.add(BasicHeader(name, value))
256         }
257         headers?.forEach { name, value ->
258             basicHeaders.add(BasicHeader(name, value))
259         }
260         return basicHeaders.toTypedArray()
261     }
262
263     // Non Blocking Rest Implementation
264     suspend fun httpClientNB(): CloseableHttpClient {
265         return HttpClients.custom()
266                 .addInterceptorFirst(WebClientUtils.logRequest())
267                 .addInterceptorLast(WebClientUtils.logResponse())
268                 .build()
269     }
270
271     //TODO maybe there could be cases where we care about return headers?
272     data class WebClientResponse<T>(val status: Int, val body: T)
273 }