Merge "Sniro BB now supports sole service proxies"
[so.git] / common / src / main / java / org / onap / so / client / RestClient.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. 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.so.client;
22
23 import java.net.ConnectException;
24 import java.net.MalformedURLException;
25 import java.net.SocketTimeoutException;
26 import java.net.URI;
27 import java.net.URL;
28 import java.security.GeneralSecurityException;
29 import java.util.ArrayList;
30 import java.util.Base64;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Optional;
36 import java.util.concurrent.TimeUnit;
37 import java.util.function.Predicate;
38 import javax.ws.rs.client.Client;
39 import javax.ws.rs.client.ClientBuilder;
40 import javax.ws.rs.client.Invocation.Builder;
41 import javax.ws.rs.client.WebTarget;
42 import javax.ws.rs.core.GenericType;
43 import javax.ws.rs.core.MediaType;
44 import javax.ws.rs.core.Response;
45 import javax.ws.rs.core.Response.Status;
46 import javax.ws.rs.core.UriBuilder;
47 import org.onap.so.client.policy.CommonObjectMapperProvider;
48 import org.onap.so.logging.jaxrs.filter.JaxRsClientLogging;
49 import org.onap.so.logging.jaxrs.filter.PayloadLoggingFilter;
50 import org.onap.so.utils.CryptoUtils;
51 import org.onap.so.utils.TargetEntity;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
55 import net.jodah.failsafe.Failsafe;
56 import net.jodah.failsafe.RetryPolicy;
57
58
59 public abstract class RestClient {
60     private static final String APPLICATION_MERGE_PATCH_JSON = "application/merge-patch+json";
61
62     public static final String ECOMP_COMPONENT_NAME = "MSO";
63
64     private static final int MAX_PAYLOAD_SIZE = 1024 * 1024;
65     private WebTarget webTarget;
66
67     protected final Map<String, String> headerMap;
68     protected final Logger logger = LoggerFactory.getLogger(RestClient.class);
69     protected URL host;
70     protected Optional<URI> path;
71     protected String accept;
72     protected String contentType;
73     protected String requestId = "";
74     protected JaxRsClientLogging jaxRsClientLogging;
75     protected RestProperties props;
76
77     protected RestClient(RestProperties props, Optional<URI> path) {
78
79         headerMap = new HashMap<>();
80         try {
81             host = props.getEndpoint();
82         } catch (MalformedURLException e) {
83
84             throw new RuntimeException(e);
85         }
86         this.props = props;
87         this.path = path;
88     }
89
90     protected RestClient(RestProperties props, Optional<URI> path, String accept, String contentType) {
91         this(props, path);
92         this.accept = accept;
93         this.contentType = contentType;
94         this.props = props;
95     }
96
97     protected RestClient(URL host, String contentType) {
98         headerMap = new HashMap<>();
99         this.path = Optional.empty();
100         this.host = host;
101         this.contentType = contentType;
102         this.props = new DefaultProperties(host);
103     }
104
105     /**
106      * Override method to return false to disable logging.
107      *
108      * @return true - to enable logging, false otherwise
109      */
110     protected boolean enableLogging() {
111         return true;
112     }
113
114     /**
115      * Override method to return custom value for max payload size.
116      *
117      * @return Default value for MAX_PAYLOAD_SIZE = 1024 * 1024
118      */
119     protected int getMaxPayloadSize() {
120         return MAX_PAYLOAD_SIZE;
121     }
122
123     protected Builder getBuilder() {
124
125         if (webTarget == null) {
126             initializeClient(getClient());
127         }
128         Builder builder = webTarget.request();
129         initializeHeaderMap(headerMap);
130         for (Entry<String, String> entry : headerMap.entrySet()) {
131             builder.header(entry.getKey(), entry.getValue());
132         }
133         return builder;
134     }
135
136     protected WebTarget getWebTarget() {
137         return this.webTarget;
138     }
139
140     protected abstract void initializeHeaderMap(Map<String, String> headerMap);
141
142     protected Optional<ResponseExceptionMapper> addResponseExceptionMapper() {
143         return Optional.of(new ResponseExceptionMapperImpl());
144     }
145
146     protected CommonObjectMapperProvider getCommonObjectMapperProvider() {
147         return new CommonObjectMapperProvider();
148     }
149
150     /**
151      * Adds a basic authentication header to the request.
152      * 
153      * @param auth the encrypted credentials
154      * @param key the key for decrypting the credentials
155      */
156     protected void addBasicAuthHeader(String auth, String key) {
157         try {
158             byte[] decryptedAuth = CryptoUtils.decrypt(auth, key).getBytes();
159             String authHeaderValue = "Basic " + Base64.getEncoder().encodeToString(decryptedAuth);
160             headerMap.put("Authorization", authHeaderValue);
161         } catch (GeneralSecurityException e) {
162             logger.error(e.getMessage(), e);
163         }
164     }
165
166     protected String getAccept() {
167         return accept;
168     }
169
170     protected String getContentType() {
171         return contentType;
172     }
173
174     protected String getMergeContentType() {
175         return APPLICATION_MERGE_PATCH_JSON;
176     }
177
178     protected Client getClient() {
179         return ClientBuilder.newBuilder().build();
180     }
181
182     protected abstract TargetEntity getTargetEntity();
183
184     protected void initializeClient(Client client) {
185         if (this.enableLogging()) {
186             client.register(new PayloadLoggingFilter(this.getMaxPayloadSize()));
187         }
188         CommonObjectMapperProvider provider = this.getCommonObjectMapperProvider();
189         client.register(new JacksonJsonProvider(provider.getMapper()));
190
191         jaxRsClientLogging = new JaxRsClientLogging();
192         jaxRsClientLogging.setTargetService(getTargetEntity());
193         client.register(jaxRsClientLogging);
194
195         if (!path.isPresent()) {
196             webTarget = client.target(host.toString());
197         } else {
198             webTarget = client.target(UriBuilder.fromUri(host + path.get().toString()));
199         }
200         if (getAccept() == null || getAccept().isEmpty()) {
201             this.accept = MediaType.APPLICATION_JSON;
202         }
203         if (getContentType() == null || getContentType().isEmpty()) {
204             this.contentType = MediaType.APPLICATION_JSON;
205         }
206     }
207
208     protected List<Predicate<Throwable>> retryOn() {
209
210         List<Predicate<Throwable>> result = new ArrayList<>();
211
212         result.add(e -> {
213             return e.getCause() instanceof SocketTimeoutException;
214         });
215         result.add(e -> {
216             return e.getCause() instanceof ConnectException;
217         });
218         return result;
219     }
220
221     public Response get() {
222         return method("GET", null);
223     }
224
225     public Response post(Object obj) {
226         return method("POST", obj);
227     }
228
229     public Response patch(Object obj) {
230         return method("PATCH", obj);
231     }
232
233     public Response put(Object obj) {
234         return method("PUT", obj);
235     }
236
237     public Response delete() {
238         return method("DELETE", null);
239     }
240
241     public Response delete(Object obj) {
242         return method("DELETE", obj);
243     }
244
245     public <T> Optional<T> get(Class<T> resultClass) {
246         return format(method("GET", null), resultClass);
247     }
248
249     public <T> Optional<T> get(GenericType<T> resultClass) {
250         return format(method("GET", null), resultClass);
251     }
252
253     public <T> T post(Object obj, Class<T> resultClass) {
254         return format(method("POST", obj), resultClass).orElse(null);
255     }
256
257     public <T> T patch(Object obj, Class<T> resultClass) {
258         return format(method("PATCH", obj), resultClass).orElse(null);
259     }
260
261     public <T> T put(Object obj, Class<T> resultClass) {
262         return format(method("PUT", obj), resultClass).orElse(null);
263     }
264
265     public <T> T put(Object obj, GenericType<T> resultClass) {
266         return format(method("PUT", obj), resultClass).orElse(null);
267     }
268
269     public <T> T delete(Class<T> resultClass) {
270         return format(method("DELETE", null), resultClass).orElse(null);
271     }
272
273     public <T> T delete(Object obj, Class<T> resultClass) {
274         return format(method("DELETE", obj), resultClass).orElse(null);
275     }
276
277     public Response method(String method, Object entity) {
278         RetryPolicy policy = new RetryPolicy();
279
280         List<Predicate<Throwable>> items = retryOn();
281
282         Predicate<Throwable> pred = items.stream().reduce(Predicate::or).orElse(x -> false);
283
284         policy.retryOn(error -> pred.test(error));
285
286         policy.withDelay(this.props.getDelayBetweenRetries(), TimeUnit.MILLISECONDS)
287                 .withMaxRetries(this.props.getRetries());
288
289         return Failsafe.with(policy).get(buildRequest(method, entity));
290     }
291
292     protected RestRequest buildRequest(String method, Object entity) {
293         return new RestRequest(this, method, entity);
294     }
295
296     private <T> Optional<T> format(Response response, Class<T> resultClass) {
297         if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
298             return Optional.empty();
299         }
300         return Optional.of(response.readEntity(resultClass));
301     }
302
303     private <T> Optional<T> format(Response response, GenericType<T> resultClass) {
304         if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
305             return Optional.empty();
306         }
307         return Optional.of(response.readEntity(resultClass));
308     }
309 }