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