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