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