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.jersey.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 abstract void initializeHeaderMap(Map<String, String> headerMap);
159 protected Optional<ResponseExceptionMapper> addResponseExceptionMapper() {
160 return Optional.of(new ResponseExceptionMapperImpl());
163 protected CommonObjectMapperProvider getCommonObjectMapperProvider() {
164 return new CommonObjectMapperProvider();
168 * Adds a basic authentication header to the request.
169 * @param auth the encrypted credentials
170 * @param key the key for decrypting the credentials
172 protected void addBasicAuthHeader(String auth, String key) {
174 byte[] decryptedAuth = CryptoUtils.decrypt(auth, key).getBytes();
175 String authHeaderValue = "Basic " + Base64.getEncoder().encodeToString(decryptedAuth);
176 headerMap.put("Authorization", authHeaderValue);
177 } catch (GeneralSecurityException e) {
178 msoLogger.error(e.getMessage(), e);
182 protected String getAccept() {
186 protected String getContentType() {
190 protected String getMergeContentType() {
191 return "application/merge-patch+json";
194 protected Client getClient() {
195 return ClientBuilder.newBuilder().build();
198 protected abstract TargetEntity getTargetEntity();
200 protected void initializeClient(Client client) {
201 if (this.enableLogging()) {
202 client.register(new LoggingFilter(this.getMaxPayloadSize()));
204 CommonObjectMapperProvider provider = this.getCommonObjectMapperProvider();
205 client.register(new JacksonJsonProvider(provider.getMapper()));
207 jaxRsClientLogging = new JaxRsClientLogging();
208 jaxRsClientLogging.setTargetService(getTargetEntity());
209 client.register(jaxRsClientLogging);
211 if (!path.isPresent()) {
212 webTarget = client.target(host.toString());
214 webTarget = client.target(UriBuilder.fromUri(host + path.get().toString()));
216 if (getAccept() == null || getAccept().isEmpty()) {
217 this.accept = MediaType.APPLICATION_JSON;
219 if (getContentType() == null || getContentType().isEmpty()) {
220 this.contentType = MediaType.APPLICATION_JSON;
224 protected List<Predicate<Throwable>> retryOn() {
226 List<Predicate<Throwable>> result = new ArrayList<>();
229 return e.getCause() instanceof SocketTimeoutException;
232 return e.getCause() instanceof ConnectException;
237 public Response get() {
238 return method("GET", null);
241 public Response post(Object obj) {
242 return method("POST", obj);
245 public Response patch(Object obj) {
246 return method("PATCH", obj);
249 public Response put(Object obj) {
250 return method("PUT", obj);
253 public Response delete() {
254 return method("DELETE", null);
257 public Response delete(Object obj) {
258 return method("DELETE", obj);
261 public <T> Optional<T> get(Class<T> resultClass) {
262 return format(method("GET", null), resultClass);
265 public <T> Optional<T> get(GenericType<T> resultClass) {
266 return format(method("GET", null), resultClass);
269 public <T> T post(Object obj, Class<T> resultClass) {
270 return format(method("POST", obj), resultClass).orElse(null);
273 public <T> T patch(Object obj, Class<T> resultClass) {
274 return format(method("PATCH", obj), resultClass).orElse(null);
277 public <T> T put(Object obj, Class<T> resultClass) {
278 return format(method("PUT", obj), resultClass).orElse(null);
281 public <T> T delete(Class<T> resultClass) {
282 return format(method("DELETE", null), resultClass).orElse(null);
285 public <T> T delete(Object obj, Class<T> resultClass) {
286 return format(method("DELETE", obj), resultClass).orElse(null);
289 private Response method(String method, Object entity) {
290 RetryPolicy policy = new RetryPolicy();
292 List<Predicate<Throwable>> items = retryOn();
294 Predicate<Throwable> pred = items.stream().reduce(Predicate::or).orElse(x -> false);
296 policy.retryOn(error -> pred.test(error));
298 policy.withDelay(this.props.getDelayBetweenRetries(), TimeUnit.MILLISECONDS)
299 .withMaxRetries(this.props.getRetries());
301 return Failsafe.with(policy).get(buildRequest(method, entity));
304 protected RestRequest buildRequest(String method, Object entity) {
305 return new RestRequest(this, method, entity);
307 private <T> Optional<T> format(Response response, Class<T> resultClass) {
308 if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
309 return Optional.empty();
311 return Optional.of(response.readEntity(resultClass));
314 private <T> Optional<T> format(Response response, GenericType<T> resultClass) {
315 if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
316 return Optional.empty();
318 return Optional.of(response.readEntity(resultClass));