Generic REST client for external services
[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 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 io.joshworks.restclient.http.HttpResponse;
24 import io.joshworks.restclient.http.JsonNode;
25 import io.joshworks.restclient.http.RestClient;
26 import io.joshworks.restclient.http.mapper.ObjectMapper;
27 import org.apache.http.impl.client.CloseableHttpClient;
28 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
29 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
30 import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
31 import org.eclipse.jetty.util.security.Password;
32 import org.apache.http.impl.client.HttpClients;
33 import org.apache.http.conn.ssl.SSLContexts;
34 import io.vavr.CheckedFunction1;
35 import lombok.SneakyThrows;
36 import lombok.val;
37
38 import java.security.UnrecoverableKeyException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.KeyManagementException;
41 import java.security.cert.CertificateException;
42 import javax.net.ssl.SSLHandshakeException;
43 import java.security.KeyStoreException;
44 import java.text.SimpleDateFormat;
45 import javax.net.ssl.SSLContext;
46 import java.io.FileInputStream;
47 import java.security.KeyStore;
48 import java.text.DateFormat;
49 import java.io.InputStream;
50 import java.io.IOException;
51 import java.util.Date;
52 import java.util.Map;
53 import java.io.File;
54 import org.onap.portalsdk.core.util.SystemProperties;
55 import org.onap.vid.properties.VidProperties;
56
57 public class SyncRestClient implements SyncRestClientInterface {
58
59     private static final String CANNOT_INITIALIZE_CUSTOM_HTTP_CLIENT = "Cannot initialize custom http client from current configuration. Using default one.";
60     private static final String TRY_TO_CALL_OVER_HTTP = "SSL Handshake problem occured. Will try to retry over Http.";
61     private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(SyncRestClient.class);
62     private static final DateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss:SSSS");
63     private static final String[] SUPPORTED_SSL_VERSIONS = {"TLSv1", "TLSv1.2"};
64     private static final String HTTPS_SCHEMA = "https://";
65     private static final String HTTP_SCHEMA = "http://";
66
67     private RestClient restClient;
68
69     public SyncRestClient() {
70         restClient = RestClient.newClient().objectMapper(defaultObjectMapper()).httpClient(defaultHttpClient()).build();
71     }
72
73     public SyncRestClient(ObjectMapper objectMapper) {
74         restClient = RestClient.newClient().objectMapper(objectMapper).httpClient(defaultHttpClient()).build();
75     }
76
77     public SyncRestClient(CloseableHttpClient httpClient) {
78         restClient = RestClient.newClient().objectMapper(defaultObjectMapper()).httpClient(httpClient).build();
79     }
80
81     public SyncRestClient(CloseableHttpClient httpClient, ObjectMapper objectMapper) {
82         restClient = RestClient.newClient().objectMapper(objectMapper).httpClient(httpClient).build();
83     }
84
85     @Override
86     public HttpResponse<JsonNode> post(String url, Map<String, String> headers, Object body) {
87         return callWithRetryOverHttp(url, url2 -> restClient.post(url2).headers(headers).body(body).asJson());
88     }
89
90     @Override
91     public <T> HttpResponse<T> post(String url, Map<String, String> headers, Object body, Class<T> responseClass) {
92         return callWithRetryOverHttp(url,
93             url2 -> restClient.post(url2).headers(headers).body(body).asObject(responseClass));
94     }
95
96     @Override
97     public HttpResponse<JsonNode> get(String url, Map<String, String> headers, Map<String, String> routeParams) {
98         return callWithRetryOverHttp(url, url2 -> {
99             val getRequest = restClient.get(url2).headers(headers);
100             routeParams.forEach(getRequest::routeParam);
101             return getRequest.asJson();
102         });
103     }
104
105     @Override
106     public <T> HttpResponse<T> get(String url, Map<String, String> headers, Map<String, String> routeParams,
107         Class<T> responseClass) {
108         return callWithRetryOverHttp(url, url2 -> {
109             val getRequest = restClient.get(url2).headers(headers);
110             routeParams.forEach(getRequest::routeParam);
111             return getRequest.asObject(responseClass);
112         });
113     }
114
115     @Override
116     public HttpResponse<InputStream> getStream(String url, Map<String, String> headers,
117         Map<String, String> routeParams) {
118         return callWithRetryOverHttp(url, url2 -> {
119             val getRequest = restClient.get(url2).headers(headers);
120             routeParams.forEach(getRequest::routeParam);
121             return getRequest.asBinary();
122         });
123     }
124
125     @Override
126     public HttpResponse<JsonNode> put(String url, Map<String, String> headers, Object body) {
127         return callWithRetryOverHttp(url, url2 -> restClient.put(url2).headers(headers).body(body).asJson());
128     }
129
130     @Override
131     public <T> HttpResponse<T> put(String url, Map<String, String> headers, Object body, Class<T> responseClass) {
132         return callWithRetryOverHttp(url,
133             url2 -> restClient.put(url2).headers(headers).body(body).asObject(responseClass));
134     }
135
136     @Override
137     public <T> HttpResponse<T> delete(String url, Map<String, String> headers, Class<T> responseClass) {
138         return callWithRetryOverHttp(url, url2 -> restClient.delete(url2).headers(headers).asObject(responseClass));
139     }
140
141     @Override
142     public HttpResponse<JsonNode> delete(String url, Map<String, String> headers) {
143         return callWithRetryOverHttp(url, url2 -> restClient.delete(url2).headers(headers).asJson());
144     }
145
146     @Override
147     public void destroy() {
148         restClient.shutdown();
149     }
150
151     @SneakyThrows
152     private <T> HttpResponse<T> callWithRetryOverHttp(String url,
153         CheckedFunction1<String, HttpResponse<T>> httpRequest) {
154         try {
155             return httpRequest.apply(url);
156         } catch (Exception e) {
157             if (e.getCause() instanceof SSLHandshakeException) {
158                 logger.warn(EELFLoggerDelegate.debugLogger,
159                     DATE_FORMAT.format(new Date()) + TRY_TO_CALL_OVER_HTTP, e);
160                 return httpRequest.apply(url.replaceFirst(HTTPS_SCHEMA, HTTP_SCHEMA));
161             }
162             throw e;
163         }
164     }
165
166     private ObjectMapper defaultObjectMapper() {
167         val objectMapper = new org.codehaus.jackson.map.ObjectMapper();
168
169         return new ObjectMapper() {
170             @Override
171             @SneakyThrows
172             public <T> T readValue(String value, Class<T> aClass) {
173                 return objectMapper.readValue(value, aClass);
174             }
175
176             @Override
177             @SneakyThrows
178             public String writeValue(Object value) {
179                 return objectMapper.writeValueAsString(value);
180             }
181         };
182     }
183
184     private CloseableHttpClient defaultHttpClient() {
185         try {
186             val trustStorePath = SystemProperties.getProperty(VidProperties.VID_TRUSTSTORE_FILENAME);
187             val trustStorePass = SystemProperties.getProperty(VidProperties.VID_TRUSTSTORE_PASSWD_X);
188             val decryptedTrustStorePass = Password.deobfuscate(trustStorePass);
189
190             val trustStore = loadTruststore(trustStorePath, decryptedTrustStorePass);
191             val sslContext = trustOwnCACerts(decryptedTrustStorePass, trustStore);
192             val sslSf = allowTLSProtocols(sslContext);
193
194             return HttpClients.custom().setSSLSocketFactory(sslSf).build();
195         } catch (IOException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
196             logger.warn(EELFLoggerDelegate.debugLogger,
197                 DATE_FORMAT.format(new Date()) + CANNOT_INITIALIZE_CUSTOM_HTTP_CLIENT, e);
198             return HttpClients.createDefault();
199         }
200     }
201
202     private SSLConnectionSocketFactory allowTLSProtocols(SSLContext sslcontext) {
203         return new SSLConnectionSocketFactory(
204             sslcontext,
205             SUPPORTED_SSL_VERSIONS,
206             null,
207             SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
208     }
209
210     private SSLContext trustOwnCACerts(String trustStorePass, KeyStore trustStore)
211         throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
212         return SSLContexts.custom()
213             .useTLS()
214             .loadKeyMaterial(trustStore, trustStorePass.toCharArray())
215             .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy())
216             .build();
217     }
218
219     private KeyStore loadTruststore(String trustStorePath, String trustStorePass)
220         throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
221         val trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
222         try (FileInputStream instream = new FileInputStream(new File(trustStorePath))) {
223             trustStore.load(instream, trustStorePass.toCharArray());
224         }
225         return trustStore;
226     }
227
228 }