2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.so.client;
23 import java.net.ConnectException;
24 import java.net.MalformedURLException;
25 import java.net.SocketTimeoutException;
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;
35 import java.util.Map.Entry;
36 import java.util.Optional;
37 import java.util.concurrent.TimeUnit;
38 import java.util.function.Predicate;
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;
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;
58 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
60 import net.jodah.failsafe.Failsafe;
61 import net.jodah.failsafe.RetryPolicy;
64 public abstract class RestClient {
65 public static final String ECOMP_COMPONENT_NAME = "MSO";
67 private static final int MAX_PAYLOAD_SIZE = 1024 * 1024;
68 private WebTarget webTarget;
70 protected final Map<String, String> headerMap;
71 protected final MsoLogger msoLogger;
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;
80 protected RestClient(RestProperties props, Optional<URI> path) {
82 msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.GENERAL, RestClient.class);
83 this.requestId = MDC.get(MsoLogger.REQUEST_ID);
84 if (requestId == null) {
87 headerMap = new HashMap<>();
89 host = props.getEndpoint();
90 } catch (MalformedURLException e) {
92 throw new RuntimeException(e);
98 protected RestClient(RestProperties props, Optional<URI> path, String accept, String contentType) {
100 this.accept = accept;
101 this.contentType = contentType;
102 this.requestId = MDC.get(MsoLogger.REQUEST_ID);
103 if (requestId == null) {
109 protected RestClient(URL host, String contentType) {
110 headerMap = new HashMap<>();
112 msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.GENERAL, RestClient.class);
113 this.path = Optional.empty();
115 this.contentType = contentType;
116 this.requestId = MDC.get(MsoLogger.REQUEST_ID);
117 if (requestId == null) {
120 this.props = new DefaultProperties(host);
124 * Override method to return false to disable logging.
126 * @return true - to enable logging, false otherwise
128 protected boolean enableLogging() {
133 * Override method to return custom value for max payload size.
135 * @return Default value for MAX_PAYLOAD_SIZE = 1024 * 1024
137 protected int getMaxPayloadSize()
139 return MAX_PAYLOAD_SIZE;
142 protected Builder getBuilder() {
144 if (webTarget == null) {
145 initializeClient(getClient());
147 Builder builder = webTarget.request();
148 initializeHeaderMap(headerMap);
150 headerMap.put("X-ECOMP-RequestID", requestId);
151 for (Entry<String, String> entry : headerMap.entrySet()) {
152 builder.header(entry.getKey(), entry.getValue());
157 protected WebTarget getWebTarget() {
158 return this.webTarget;
161 protected abstract void initializeHeaderMap(Map<String, String> headerMap);
163 protected Optional<ResponseExceptionMapper> addResponseExceptionMapper() {
164 return Optional.of(new ResponseExceptionMapperImpl());
167 protected CommonObjectMapperProvider getCommonObjectMapperProvider() {
168 return new CommonObjectMapperProvider();
172 * Adds a basic authentication header to the request.
173 * @param auth the encrypted credentials
174 * @param key the key for decrypting the credentials
176 protected void addBasicAuthHeader(String auth, String key) {
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);
186 protected String getAccept() {
190 protected String getContentType() {
194 protected String getMergeContentType() {
195 return "application/merge-patch+json";
198 protected Client getClient() {
199 return ClientBuilder.newBuilder().build();
202 protected abstract TargetEntity getTargetEntity();
204 protected void initializeClient(Client client) {
205 if (this.enableLogging()) {
206 client.register(new LoggingFilter(this.getMaxPayloadSize()));
208 CommonObjectMapperProvider provider = this.getCommonObjectMapperProvider();
209 client.register(new JacksonJsonProvider(provider.getMapper()));
211 jaxRsClientLogging = new JaxRsClientLogging();
212 jaxRsClientLogging.setTargetService(getTargetEntity());
213 client.register(jaxRsClientLogging);
215 if (!path.isPresent()) {
216 webTarget = client.target(host.toString());
218 webTarget = client.target(UriBuilder.fromUri(host + path.get().toString()));
220 if (getAccept() == null || getAccept().isEmpty()) {
221 this.accept = MediaType.APPLICATION_JSON;
223 if (getContentType() == null || getContentType().isEmpty()) {
224 this.contentType = MediaType.APPLICATION_JSON;
228 protected List<Predicate<Throwable>> retryOn() {
230 List<Predicate<Throwable>> result = new ArrayList<>();
233 return e.getCause() instanceof SocketTimeoutException;
236 return e.getCause() instanceof ConnectException;
241 public Response get() {
242 return method("GET", null);
245 public Response post(Object obj) {
246 return method("POST", obj);
249 public Response patch(Object obj) {
250 return method("PATCH", obj);
253 public Response put(Object obj) {
254 return method("PUT", obj);
257 public Response delete() {
258 return method("DELETE", null);
261 public Response delete(Object obj) {
262 return method("DELETE", obj);
265 public <T> Optional<T> get(Class<T> resultClass) {
266 return format(method("GET", null), resultClass);
269 public <T> Optional<T> get(GenericType<T> resultClass) {
270 return format(method("GET", null), resultClass);
273 public <T> T post(Object obj, Class<T> resultClass) {
274 return format(method("POST", obj), resultClass).orElse(null);
277 public <T> T patch(Object obj, Class<T> resultClass) {
278 return format(method("PATCH", obj), resultClass).orElse(null);
281 public <T> T put(Object obj, Class<T> resultClass) {
282 return format(method("PUT", obj), resultClass).orElse(null);
285 public <T> T delete(Class<T> resultClass) {
286 return format(method("DELETE", null), resultClass).orElse(null);
289 public <T> T delete(Object obj, Class<T> resultClass) {
290 return format(method("DELETE", obj), resultClass).orElse(null);
293 private Response method(String method, Object entity) {
294 RetryPolicy policy = new RetryPolicy();
296 List<Predicate<Throwable>> items = retryOn();
298 Predicate<Throwable> pred = items.stream().reduce(Predicate::or).orElse(x -> false);
300 policy.retryOn(error -> pred.test(error));
302 policy.withDelay(this.props.getDelayBetweenRetries(), TimeUnit.MILLISECONDS)
303 .withMaxRetries(this.props.getRetries());
305 return Failsafe.with(policy).get(buildRequest(method, entity));
308 protected RestRequest buildRequest(String method, Object entity) {
309 return new RestRequest(this, method, entity);
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();
315 return Optional.of(response.readEntity(resultClass));
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();
322 return Optional.of(response.readEntity(resultClass));