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