130b6c15938dac682e4d7069ef49f54109a6c487
[policy/common.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2018 Samsung Electronics Co., Ltd.
7  * Modifications Copyright (C) 2019, 2023 Nordix Foundation.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.common.endpoints.http.client.internal;
24
25 import com.google.re2j.Pattern;
26 import jakarta.ws.rs.client.Client;
27 import jakarta.ws.rs.client.ClientBuilder;
28 import jakarta.ws.rs.client.Entity;
29 import jakarta.ws.rs.client.Invocation.Builder;
30 import jakarta.ws.rs.client.InvocationCallback;
31 import jakarta.ws.rs.client.WebTarget;
32 import jakarta.ws.rs.core.Response;
33 import java.security.KeyManagementException;
34 import java.security.NoSuchAlgorithmException;
35 import java.security.SecureRandom;
36 import java.util.Collections;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.concurrent.Future;
40 import javax.net.ssl.SSLContext;
41 import lombok.Getter;
42 import lombok.ToString;
43 import org.apache.commons.lang3.StringUtils;
44 import org.glassfish.jersey.client.ClientProperties;
45 import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
46 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
47 import org.onap.policy.common.endpoints.http.client.HttpClient;
48 import org.onap.policy.common.utils.network.NetworkUtil;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * Http Client implementation using a Jersey Client.
54  */
55 @Getter
56 @ToString
57 public class JerseyClient implements HttpClient {
58     private static final Pattern COMMA_PAT = Pattern.compile(",");
59
60     /**
61      * Logger.
62      */
63     private static final Logger logger = LoggerFactory.getLogger(JerseyClient.class);
64
65     protected static final String JERSEY_DEFAULT_SERIALIZATION_PROVIDER =
66                     "org.onap.policy.common.gson.GsonMessageBodyHandler";
67
68     protected final String name;
69     protected final boolean https;
70     protected final boolean selfSignedCerts;
71     protected final String hostname;
72     protected final int port;
73     protected final String basePath;
74     protected final String userName;
75     protected final String password;
76
77     protected final Client client;
78     protected final String baseUrl;
79
80     protected boolean alive = true;
81
82     /**
83      * Constructor.
84      *
85      * <p>name - the name
86      * https - is it https or not
87      * selfSignedCerts - are there self-signed certs
88      * hostname - the hostname
89      * port - port being used
90      * basePath - base context
91      * userName - user credentials
92      * password - password credentials
93      *
94      * @param busTopicParams Input parameters object
95      * @throws KeyManagementException key exception
96      * @throws NoSuchAlgorithmException no algorithm exception
97      * @throws ClassNotFoundException if the serialization provider cannot be found
98      */
99     public JerseyClient(BusTopicParams busTopicParams)
100                     throws KeyManagementException, NoSuchAlgorithmException, ClassNotFoundException {
101
102         if (busTopicParams.isClientNameInvalid()) {
103             throw new IllegalArgumentException("Name must be provided");
104         }
105
106         if (busTopicParams.isHostnameInvalid()) {
107             throw new IllegalArgumentException("Hostname must be provided");
108         }
109
110         if (busTopicParams.isPortInvalid()) {
111             throw new IllegalArgumentException("Invalid Port provided: " + busTopicParams.getPort());
112         }
113
114         this.name = busTopicParams.getClientName();
115         this.https = busTopicParams.isUseHttps();
116         this.hostname = busTopicParams.getHostname();
117         this.port = busTopicParams.getPort();
118         this.basePath = busTopicParams.getBasePath();
119         this.userName = busTopicParams.getUserName();
120         this.password = busTopicParams.getPassword();
121         this.selfSignedCerts = busTopicParams.isAllowSelfSignedCerts();
122         this.client = detmClient();
123
124         if (!StringUtils.isBlank(this.userName) && !StringUtils.isBlank(this.password)) {
125             var authFeature = HttpAuthenticationFeature.basic(userName, password);
126             this.client.register(authFeature);
127         }
128
129         this.client.property(ClientProperties.METAINF_SERVICES_LOOKUP_DISABLE, "true");
130
131         registerSerProviders(busTopicParams.getSerializationProvider());
132
133         this.baseUrl = (this.https ? "https://" : "http://") + this.hostname + ":" + this.port + "/"
134                         + (this.basePath == null ? "" : this.basePath);
135     }
136
137     private Client detmClient() throws NoSuchAlgorithmException, KeyManagementException {
138         if (this.https) {
139             ClientBuilder clientBuilder;
140             var sslContext = SSLContext.getInstance("TLSv1.2");
141             if (this.selfSignedCerts) {
142                 sslContext.init(null, NetworkUtil.getAlwaysTrustingManager(), new SecureRandom());
143
144                 // This falls under self-signed certs which is used for non-production testing environments where
145                 // the hostname in the cert is unlikely to be crafted properly.  We always return true for the
146                 // hostname verifier.  This causes a sonar vuln, but we ignore it as it could cause problems in some
147                 // testing environments.
148                 clientBuilder =
149                         ClientBuilder.newBuilder().sslContext(sslContext).hostnameVerifier(
150                             (host, session) -> true); //NOSONAR
151             } else {
152                 sslContext.init(null, null, null);
153                 clientBuilder = ClientBuilder.newBuilder().sslContext(sslContext);
154             }
155             return clientBuilder.build();
156
157         } else {
158             return ClientBuilder.newClient();
159         }
160     }
161
162     /**
163      * Registers the serialization provider(s) with the client.
164      *
165      * @param serializationProvider comma-separated list of serialization providers
166      * @throws ClassNotFoundException if the serialization provider cannot be found
167      */
168     private void registerSerProviders(String serializationProvider) throws ClassNotFoundException {
169         String providers = (StringUtils.isBlank(serializationProvider)
170                         ? JERSEY_DEFAULT_SERIALIZATION_PROVIDER : serializationProvider);
171         for (String prov : COMMA_PAT.split(providers)) {
172             this.client.register(Class.forName(prov));
173         }
174     }
175
176     @Override
177     public WebTarget getWebTarget() {
178         return this.client.target(this.baseUrl);
179     }
180
181     @Override
182     public Response get(String path) {
183         if (!StringUtils.isBlank(path)) {
184             return getWebTarget().path(path).request().get();
185         } else {
186             return getWebTarget().request().get();
187         }
188     }
189
190     @Override
191     public Response get() {
192         return getWebTarget().request().get();
193     }
194
195     @Override
196     public Future<Response> get(InvocationCallback<Response> callback, String path, Map<String, Object> headers) {
197         Map<String, Object> headers2 = (headers != null ? headers : Collections.emptyMap());
198
199         if (!StringUtils.isBlank(path)) {
200             return getBuilder(path, headers2).async().get(callback);
201         } else {
202             return get(callback, headers2);
203         }
204     }
205
206     @Override
207     public Future<Response> get(InvocationCallback<Response> callback, Map<String, Object> headers) {
208         var builder = getWebTarget().request();
209         if (headers != null) {
210             headers.forEach(builder::header);
211         }
212         return builder.async().get(callback);
213     }
214
215     @Override
216     public Response put(String path, Entity<?> entity, Map<String, Object> headers) {
217         return getBuilder(path, headers).put(entity);
218     }
219
220     @Override
221     public Future<Response> put(InvocationCallback<Response> callback, String path, Entity<?> entity,
222                     Map<String, Object> headers) {
223         return getBuilder(path, headers).async().put(entity, callback);
224     }
225
226     @Override
227     public Response post(String path, Entity<?> entity, Map<String, Object> headers) {
228         return getBuilder(path, headers).post(entity);
229     }
230
231     @Override
232     public Future<Response> post(InvocationCallback<Response> callback, String path, Entity<?> entity,
233                     Map<String, Object> headers) {
234         return getBuilder(path, headers).async().post(entity, callback);
235     }
236
237     @Override
238     public Response delete(String path, Map<String, Object> headers) {
239         return getBuilder(path, headers).delete();
240     }
241
242     @Override
243     public Future<Response> delete(InvocationCallback<Response> callback, String path, Map<String, Object> headers) {
244         return getBuilder(path, headers).async().delete(callback);
245     }
246
247     @Override
248     public boolean start() {
249         return alive;
250     }
251
252     @Override
253     public boolean stop() {
254         return !alive;
255     }
256
257     @Override
258     public void shutdown() {
259         synchronized (this) {
260             alive = false;
261         }
262
263         try {
264             this.client.close();
265         } catch (Exception e) {
266             logger.warn("{}: cannot close because of {}", this, e.getMessage(), e);
267         }
268     }
269
270     @Override
271     public synchronized boolean isAlive() {
272         return this.alive;
273     }
274
275     private Builder getBuilder(String path, Map<String, Object> headers) {
276         var builder = getWebTarget().path(path).request();
277         for (Entry<String, Object> header : headers.entrySet()) {
278             builder.header(header.getKey(), header.getValue());
279         }
280         return builder;
281     }
282
283
284 }