Merge "Patch NPE in Unirest HttpResponse::getBody when getRawBody is null"
[vid.git] / vid-app-common / src / main / java / org / onap / vid / client / SyncRestClient.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2018 - 2019 Nokia. All rights reserved.
6  * ================================================================================
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.vid.client;
22
23 import static org.apache.commons.lang3.StringUtils.isEmpty;
24 import static org.onap.vid.client.UnirestPatchKt.patched;
25
26 import io.joshworks.restclient.http.HttpResponse;
27 import io.joshworks.restclient.http.JsonNode;
28 import io.joshworks.restclient.http.RestClient;
29 import io.joshworks.restclient.http.exceptions.RestClientException;
30 import io.joshworks.restclient.http.mapper.ObjectMapper;
31 import io.joshworks.restclient.request.GetRequest;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.security.KeyManagementException;
37 import java.security.KeyStore;
38 import java.security.KeyStoreException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.UnrecoverableKeyException;
41 import java.security.cert.CertificateException;
42 import java.util.Map;
43 import javax.net.ssl.SSLContext;
44 import javax.net.ssl.SSLException;
45 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
46 import org.apache.http.conn.ssl.SSLContexts;
47 import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
48 import org.apache.http.impl.client.CloseableHttpClient;
49 import org.apache.http.impl.client.HttpClients;
50 import org.eclipse.jetty.util.security.Password;
51 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
52 import org.onap.portalsdk.core.util.SystemProperties;
53 import org.onap.vid.properties.VidProperties;
54
55 public class SyncRestClient implements SyncRestClientInterface {
56     private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(SyncRestClient.class);
57     private static final String[] SUPPORTED_SSL_VERSIONS = {"TLSv1", "TLSv1.2"};
58     private static final String HTTPS_SCHEMA = "https://";
59     private static final String HTTP_SCHEMA = "http://";
60
61     private RestClient restClient;
62
63     public SyncRestClient() {
64         restClient = RestClient.newClient().objectMapper(defaultObjectMapper()).httpClient(defaultHttpClient()).build();
65     }
66
67     public SyncRestClient(ObjectMapper objectMapper) {
68         restClient = RestClient.newClient().objectMapper(objectMapper).httpClient(defaultHttpClient()).build();
69     }
70
71     public SyncRestClient(CloseableHttpClient httpClient) {
72         restClient = RestClient.newClient().objectMapper(defaultObjectMapper()).httpClient(httpClient).build();
73     }
74
75     public SyncRestClient(CloseableHttpClient httpClient, ObjectMapper objectMapper) {
76         restClient = RestClient.newClient().objectMapper(objectMapper).httpClient(httpClient).build();
77     }
78
79     @Override
80     public HttpResponse<JsonNode> post(String url, Map<String, String> headers, Object body) {
81         return callWithRetryOverHttp(url, url2 -> restClient.post(url2).headers(headers).body(body).asJson());
82     }
83
84     @Override
85     public <T> HttpResponse<T> post(String url, Map<String, String> headers, Object body, Class<T> responseClass) {
86         return callWithRetryOverHttp(url,
87                 url2 -> restClient.post(url2).headers(headers).body(body).asObject(responseClass));
88     }
89
90     @Override
91     public HttpResponse<JsonNode> get(String url, Map<String, String> headers, Map<String, String> routeParams) {
92         return callWithRetryOverHttp(url, url2 -> {
93             GetRequest getRequest = restClient.get(url2).headers(headers);
94             routeParams.forEach(getRequest::routeParam);
95             return getRequest.asJson();
96         });
97     }
98
99     @Override
100     public <T> HttpResponse<T> get(String url, Map<String, String> headers, Map<String, String> routeParams,
101                                    Class<T> responseClass) {
102         return callWithRetryOverHttp(url, url2 -> {
103             GetRequest getRequest = restClient.get(url2).headers(headers);
104             routeParams.forEach(getRequest::routeParam);
105             return getRequest.asObject(responseClass);
106         });
107     }
108
109     @Override
110     public HttpResponse<InputStream> getStream(String url, Map<String, String> headers,
111                                                Map<String, String> routeParams) {
112         return callWithRetryOverHttp(url, url2 -> {
113             GetRequest getRequest = restClient.get(url2).headers(headers);
114             routeParams.forEach(getRequest::routeParam);
115             return getRequest.asBinary();
116         });
117     }
118
119     @Override
120     public HttpResponse<JsonNode> put(String url, Map<String, String> headers, Object body) {
121         return callWithRetryOverHttp(url, url2 -> restClient.put(url2).headers(headers).body(body).asJson());
122     }
123
124     @Override
125     public <T> HttpResponse<T> put(String url, Map<String, String> headers, Object body, Class<T> responseClass) {
126         return callWithRetryOverHttp(url,
127                 url2 -> restClient.put(url2).headers(headers).body(body).asObject(responseClass));
128     }
129
130     @Override
131     public <T> HttpResponse<T> delete(String url, Map<String, String> headers, Object body, Class<T> responseClass) {
132         return callWithRetryOverHttp(url, url2 -> restClient.delete(url2).headers(headers).body(body).asObject(responseClass));
133     }
134
135     @Override
136     public <T> HttpResponse<T> delete(String url, Map<String, String> headers, Class<T> responseClass) {
137         return callWithRetryOverHttp(url, url2 -> restClient.delete(url2).headers(headers).asObject(responseClass));
138     }
139
140     @Override
141     public HttpResponse<JsonNode> delete(String url, Map<String, String> headers) {
142         return callWithRetryOverHttp(url, url2 -> restClient.delete(url2).headers(headers).asJson());
143     }
144
145     @Override
146     public void destroy() {
147         restClient.shutdown();
148     }
149
150     private <T> HttpResponse<T> callWithRetryOverHttp(String url, HttpRequest<T> httpRequest) {
151         try {
152             return callWithRetryOverHttpThrows(url, httpRequest);
153         } catch (IOException e) {
154             throw new SyncRestClientException("IOException while calling rest service", e);
155         }
156     }
157
158     private <T> HttpResponse<T> callWithRetryOverHttpThrows(String url, HttpRequest<T> httpRequest) throws IOException {
159         try {
160             return patched(httpRequest.apply(url));
161         } catch (RestClientException e) {
162             if (causedBySslHandshakeError(e)) {
163                 logger.warn(EELFLoggerDelegate.debugLogger, "SSL Handshake problem occured. Will try to retry over Http.", e);
164                 return patched(httpRequest.apply(url.replaceFirst(HTTPS_SCHEMA, HTTP_SCHEMA)));
165             }
166             throw e;
167         }
168     }
169
170     private boolean causedBySslHandshakeError(RestClientException exception) {
171         return exception.getCause() instanceof SSLException;
172     }
173
174     private ObjectMapper defaultObjectMapper() {
175         com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
176
177         return new ObjectMapper() {
178             @Override
179             public <T> T readValue(String value, Class<T> aClass) {
180                 try {
181                     return isEmpty(value) ? null : objectMapper.readValue(value, aClass);
182                 } catch (IOException e) {
183                     throw new SyncRestClientException("IOException while reading value", e);
184                 }
185             }
186
187             @Override
188             public String writeValue(Object value) {
189                 try {
190                     return objectMapper.writeValueAsString(value);
191                 } catch (IOException e) {
192                     throw new SyncRestClientException("IOException while writing value", e);
193                 }
194             }
195         };
196     }
197
198     private CloseableHttpClient defaultHttpClient() {
199         try {
200             String trustStorePath = SystemProperties.getProperty(VidProperties.VID_TRUSTSTORE_FILENAME);
201             String trustStorePass = SystemProperties.getProperty(VidProperties.VID_TRUSTSTORE_PASSWD_X);
202             String decryptedTrustStorePass = Password.deobfuscate(trustStorePass);
203
204             KeyStore trustStore = loadTruststore(trustStorePath, decryptedTrustStorePass);
205             SSLContext sslContext = trustOwnCACerts(decryptedTrustStorePass, trustStore);
206             SSLConnectionSocketFactory sslSf = allowTLSProtocols(sslContext);
207
208             return HttpClients.custom().setSSLSocketFactory(sslSf).build();
209         } catch (IOException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
210             logger.warn(EELFLoggerDelegate.debugLogger, "Cannot initialize custom http client from current configuration. Using default one.", e);
211             return HttpClients.createDefault();
212         }
213     }
214
215     private SSLConnectionSocketFactory allowTLSProtocols(SSLContext sslcontext) {
216         return new SSLConnectionSocketFactory(
217                 sslcontext,
218                 SUPPORTED_SSL_VERSIONS,
219                 null,
220                 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
221     }
222
223     private SSLContext trustOwnCACerts(String trustStorePass, KeyStore trustStore)
224             throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
225         return SSLContexts.custom()
226                 .useTLS()
227                 .loadKeyMaterial(trustStore, trustStorePass.toCharArray())
228                 .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
229                 .build();
230     }
231
232     private KeyStore loadTruststore(String trustStorePath, String trustStorePass)
233             throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
234         KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
235         try (FileInputStream instream = new FileInputStream(new File(trustStorePath))) {
236             trustStore.load(instream, trustStorePass.toCharArray());
237         }
238         return trustStore;
239     }
240
241     @FunctionalInterface
242     private interface HttpRequest<T> {
243         HttpResponse<T> apply(String url) throws IOException;
244     }
245
246 }