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