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;
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;
60 public abstract class RestClient {
61 private static final String APPLICATION_MERGE_PATCH_JSON = "application/merge-patch+json";
63 public static final String ECOMP_COMPONENT_NAME = "MSO";
65 private static final int MAX_PAYLOAD_SIZE = 1024 * 1024;
66 private WebTarget webTarget;
68 protected final Map<String, String> headerMap;
69 protected final Logger logger = LoggerFactory.getLogger(RestClient.class);
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;
78 protected RestClient(RestProperties props, Optional<URI> path) {
80 headerMap = new HashMap<>();
82 host = props.getEndpoint();
83 } catch (MalformedURLException e) {
85 throw new RuntimeException(e);
91 protected RestClient(RestProperties props, Optional<URI> path, String accept, String contentType) {
94 this.contentType = contentType;
98 protected RestClient(URL host, String contentType) {
99 headerMap = new HashMap<>();
100 this.path = Optional.empty();
102 this.contentType = contentType;
103 this.props = new DefaultProperties(host);
107 * Override method to return false to disable logging.
109 * @return true - to enable logging, false otherwise
111 protected boolean enableLogging() {
116 * Override method to return custom value for max payload size.
118 * @return Default value for MAX_PAYLOAD_SIZE = 1024 * 1024
120 protected int getMaxPayloadSize()
122 return MAX_PAYLOAD_SIZE;
125 protected Builder getBuilder() {
127 if (webTarget == null) {
128 initializeClient(getClient());
130 Builder builder = webTarget.request();
131 initializeHeaderMap(headerMap);
132 for (Entry<String, String> entry : headerMap.entrySet()) {
133 builder.header(entry.getKey(), entry.getValue());
138 protected WebTarget getWebTarget() {
139 return this.webTarget;
142 protected abstract void initializeHeaderMap(Map<String, String> headerMap);
144 protected Optional<ResponseExceptionMapper> addResponseExceptionMapper() {
145 return Optional.of(new ResponseExceptionMapperImpl());
148 protected CommonObjectMapperProvider getCommonObjectMapperProvider() {
149 return new CommonObjectMapperProvider();
153 * Adds a basic authentication header to the request.
154 * @param auth the encrypted credentials
155 * @param key the key for decrypting the credentials
157 protected void addBasicAuthHeader(String auth, String key) {
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);
167 protected String getAccept() {
171 protected String getContentType() {
175 protected String getMergeContentType() {
176 return APPLICATION_MERGE_PATCH_JSON;
179 protected Client getClient() {
180 return ClientBuilder.newBuilder().build();
183 protected abstract TargetEntity getTargetEntity();
185 protected void initializeClient(Client client) {
186 if (this.enableLogging()) {
187 client.register(new PayloadLoggingFilter(this.getMaxPayloadSize()));
189 CommonObjectMapperProvider provider = this.getCommonObjectMapperProvider();
190 client.register(new JacksonJsonProvider(provider.getMapper()));
192 jaxRsClientLogging = new JaxRsClientLogging();
193 jaxRsClientLogging.setTargetService(getTargetEntity());
194 client.register(jaxRsClientLogging);
196 if (!path.isPresent()) {
197 webTarget = client.target(host.toString());
199 webTarget = client.target(UriBuilder.fromUri(host + path.get().toString()));
201 if (getAccept() == null || getAccept().isEmpty()) {
202 this.accept = MediaType.APPLICATION_JSON;
204 if (getContentType() == null || getContentType().isEmpty()) {
205 this.contentType = MediaType.APPLICATION_JSON;
209 protected List<Predicate<Throwable>> retryOn() {
211 List<Predicate<Throwable>> result = new ArrayList<>();
214 return e.getCause() instanceof SocketTimeoutException;
217 return e.getCause() instanceof ConnectException;
222 public Response get() {
223 return method("GET", null);
226 public Response post(Object obj) {
227 return method("POST", obj);
230 public Response patch(Object obj) {
231 return method("PATCH", obj);
234 public Response put(Object obj) {
235 return method("PUT", obj);
238 public Response delete() {
239 return method("DELETE", null);
242 public Response delete(Object obj) {
243 return method("DELETE", obj);
246 public <T> Optional<T> get(Class<T> resultClass) {
247 return format(method("GET", null), resultClass);
250 public <T> Optional<T> get(GenericType<T> resultClass) {
251 return format(method("GET", null), resultClass);
254 public <T> T post(Object obj, Class<T> resultClass) {
255 return format(method("POST", obj), resultClass).orElse(null);
258 public <T> T patch(Object obj, Class<T> resultClass) {
259 return format(method("PATCH", obj), resultClass).orElse(null);
262 public <T> T put(Object obj, Class<T> resultClass) {
263 return format(method("PUT", obj), resultClass).orElse(null);
266 public <T> T delete(Class<T> resultClass) {
267 return format(method("DELETE", null), resultClass).orElse(null);
270 public <T> T delete(Object obj, Class<T> resultClass) {
271 return format(method("DELETE", obj), resultClass).orElse(null);
274 public Response method(String method, Object entity) {
275 RetryPolicy policy = new RetryPolicy();
277 List<Predicate<Throwable>> items = retryOn();
279 Predicate<Throwable> pred = items.stream().reduce(Predicate::or).orElse(x -> false);
281 policy.retryOn(error -> pred.test(error));
283 policy.withDelay(this.props.getDelayBetweenRetries(), TimeUnit.MILLISECONDS)
284 .withMaxRetries(this.props.getRetries());
286 return Failsafe.with(policy).get(buildRequest(method, entity));
289 protected RestRequest buildRequest(String method, Object entity) {
290 return new RestRequest(this, method, entity);
292 private <T> Optional<T> format(Response response, Class<T> resultClass) {
293 if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
294 return Optional.empty();
296 return Optional.of(response.readEntity(resultClass));
299 private <T> Optional<T> format(Response response, GenericType<T> resultClass) {
300 if (this.props.mapNotFoundToEmpty() && response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
301 return Optional.empty();
303 return Optional.of(response.readEntity(resultClass));