2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 AT&T Intellectual Property.
6 * Copyright © 2017 Amdocs
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 * ECOMP and OpenECOMP are trademarks
23 * and service marks of AT&T Intellectual Property.
25 package org.openecomp.restclient.client;
27 import com.sun.jersey.api.client.Client;
28 import com.sun.jersey.api.client.ClientResponse;
29 import com.sun.jersey.api.client.WebResource;
30 import com.sun.jersey.api.client.WebResource.Builder;
31 import com.sun.jersey.core.util.MultivaluedMapImpl;
33 import org.openecomp.cl.api.LogFields;
34 import org.openecomp.cl.api.LogLine;
35 import org.openecomp.cl.api.Logger;
36 import org.openecomp.cl.eelf.LoggerFactory;
37 import org.openecomp.cl.mdc.MdcContext;
38 import org.openecomp.cl.mdc.MdcOverride;
39 import org.openecomp.restclient.logging.RestClientMsgs;
40 import org.openecomp.restclient.rest.RestClientBuilder;
42 import java.io.ByteArrayOutputStream;
43 import java.text.SimpleDateFormat;
44 import java.util.Arrays;
45 import java.util.List;
47 import java.util.Map.Entry;
48 import java.util.UUID;
50 import javax.ws.rs.core.MediaType;
51 import javax.ws.rs.core.MultivaluedMap;
52 import javax.ws.rs.core.Response;
56 * This class provides a general client implementation that micro services can use for communicating
57 * with the endpoints via their exposed REST interfaces.
59 public class RestClient {
62 * This is a generic builder that is used for constructing the REST client that we will use to
63 * communicate with the REST endpoint.
65 private RestClientBuilder clientBuilder;
68 * The low level instance of the REST client that will be used to communicate with the endpoint.
70 private Client restClient = null;
72 /** Standard logger for producing log statements. */
73 private Logger logger = LoggerFactory.getInstance().getLogger("AAIRESTClient");
75 /** Standard logger for producing metric statements. */
76 private Logger metricsLogger = LoggerFactory.getInstance().getMetricsLogger("AAIRESTClient");
78 private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
80 /** Reusable function call for GET REST operations. */
81 private final RestOperation getOp = new GetRestOperation();
83 /** Reusable function call for PUT REST operations. */
84 private final RestOperation putOp = new PutRestOperation();
86 /** Reusable function call for POST REST operations. */
87 private final RestOperation postOp = new PostRestOperation();
89 /** Reusable function call for DELETE REST operations. */
90 private final RestOperation deleteOp = new DeleteRestOperation();
93 * Creates a new instance of the {@link RestClient}.
96 clientBuilder = new RestClientBuilder();
101 * Creates a new instance of the {@link RestClient} using the supplied {@link RestClientBuilder}.
103 * @param rcBuilder - The REST client builder that this instance of the {@link RestClient} should
106 public RestClient(RestClientBuilder rcBuilder) {
107 clientBuilder = rcBuilder;
112 * Sets the flag to indicate whether or not validation should be performed against the host name
113 * of the server we are trying to communicate with.
115 * @parameter validate - Set to true to enable validation, false to disable
117 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
119 public RestClient validateServerHostname(boolean validate) {
120 logger.debug("Set validate server hostname = " + validate);
121 clientBuilder.setValidateServerHostname(validate);
127 * Sets the flag to indicate whether or not validation should be performed against the certificate
130 * @parameter validate - Set to true to enable validation, false to disable.
132 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
134 public RestClient validateServerCertChain(boolean validate) {
135 logger.debug("Set validate server certificate chain = " + validate);
136 clientBuilder.setValidateServerCertChain(validate);
142 * Assigns the client certificate file to use.
144 * @param filename - The name of the certificate file.
146 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
148 public RestClient clientCertFile(String filename) {
149 logger.debug("Set client certificate filename = " + filename);
150 clientBuilder.setClientCertFileName(filename);
156 * Assigns the client certificate password to use.
158 * @param password - The certificate password.
160 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
162 public RestClient clientCertPassword(String password) {
163 logger.debug("Set client certificate password = " + password);
164 clientBuilder.setClientCertPassword(password);
170 * Assigns the name of the trust store file to use.
172 * @param filename - the name of the trust store file.
174 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
176 public RestClient trustStore(String filename) {
177 logger.debug("Set trust store filename = " + filename);
178 clientBuilder.setTruststoreFilename(filename);
184 * Assigns the connection timeout (in ms) to use when connecting to the target server.
186 * @param timeout - The length of time to wait in ms before timing out.
188 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
190 public RestClient connectTimeoutMs(int timeout) {
191 logger.debug("Set connection timeout = " + timeout + " ms");
192 clientBuilder.setConnectTimeoutInMs(timeout);
198 * Assigns the read timeout (in ms) to use when communicating with the target server.
200 * @param timeout The read timeout in milliseconds.
202 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
204 public RestClient readTimeoutMs(int timeout) {
205 logger.debug("Set read timeout = " + timeout + " ms");
206 clientBuilder.setReadTimeoutInMs(timeout);
211 * This method operates on a REST endpoint by submitting an HTTP operation request against the
213 * This variant of the method will perform a requested number of retries in the event that the
214 * first request is unsuccessful.
216 * @param operation - the REST operation type to send to the url
217 * @param url - The REST endpoint to submit the REST request to.
218 * @param payload - They payload to provide in the REST request, if applicable
219 * @param headers - The headers that should be passed in the request
220 * @param contentType - The content type of the payload
221 * @param responseType - The expected format of the response.
223 * @return The result of the REST request.
225 protected OperationResult processRequest(RestOperation operation, String url, String payload,
226 Map<String, List<String>> headers, MediaType contentType, MediaType responseType,
230 OperationResult result = null;
232 long startTimeInMs = System.currentTimeMillis();
233 for (int retryCount = 0; retryCount < numRetries; retryCount++) {
235 logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, url, Integer.toString(retryCount + 1));
237 // Submit our query to the AAI.
238 result = processRequest(operation, url, payload, headers, contentType, responseType);
240 // If the submission was successful then we're done.
241 if (Integer.toString(result.getResultCode()).charAt(0) == '2') {
242 logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES,
243 Long.toString(System.currentTimeMillis() - startTimeInMs), url,
244 Integer.toString(retryCount));
248 // Our submission was unsuccessful...
250 // Sleep between re-tries to be nice to the target system.
253 } catch (InterruptedException e) {
254 logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage());
259 // If we've gotten this far, then we failed all of our retries.
260 result.setResultCode(504);
261 result.setFailureCause(
262 "Failed to get a successful result " + "after multiple retries to target server");
268 * This method operates on a REST endpoint by submitting an HTTP operation request against the
271 * @param operation - the REST operation type to send to the url
272 * @param url - The REST endpoint to submit the REST request to.
273 * @param payload - They payload to provide in the REST request, if applicable
274 * @param headers - The headers that should be passed in the request
275 * @param contentType - The content type of the payload
276 * @param responseType - The expected format of the response.
278 * @return The result of the REST request.
280 protected OperationResult processRequest(RestOperation operation, String url, String payload,
281 Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
283 ClientResponse clientResponse = null;
284 OperationResult operationResult = new OperationResult();
285 ByteArrayOutputStream baos = new ByteArrayOutputStream();
287 String requestType = operation.getRequestType().name();
289 // Grab the current time so that we can log how long the
290 // query took once we are done.
291 long startTimeInMs = System.currentTimeMillis();
292 MdcOverride override = new MdcOverride();
293 override.addAttribute(MdcContext.MDC_START_TIME, formatter.format(startTimeInMs));
295 logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url);
299 // Get a REST client instance for our request.
300 Client client = getClient();
302 // Debug log the request
303 debugRequest(url, payload, headers, responseType);
305 // Get a client request builder, and submit our GET request.
306 Builder builder = getClientBuilder(client, url, payload, headers, contentType, responseType);
307 clientResponse = operation.processOperation(builder);
309 populateOperationResult(clientResponse, operationResult);
311 // Debug log the response
312 debugResponse(operationResult, clientResponse.getHeaders());
314 } catch (Exception ex) {
316 logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage());
317 operationResult.setResultCode(500);
318 operationResult.setFailureCause(
319 "Error during GET operation to AAI with message = " + ex.getLocalizedMessage());
323 if (logger.isDebugEnabled()) {
324 logger.debug(baos.toString());
327 // Not every valid response code is actually represented by the Response.Status
328 // object, so we need to guard against missing codes, otherwise we throw null
329 // pointer exceptions when we try to generate our metrics logs...
330 Response.Status responseStatus =
331 Response.Status.fromStatusCode(operationResult.getResultCode());
332 String responseStatusCodeString = "";
333 if (responseStatus != null) {
334 responseStatusCodeString = responseStatus.toString();
337 metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME,
338 new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString)
339 .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode())
340 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()),
341 override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url);
342 logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType,
343 Long.toString(System.currentTimeMillis() - startTimeInMs), url);
344 logger.info(RestClientMsgs.HTTP_RESPONSE, url,
345 operationResult.getResultCode() + " " + responseStatusCodeString);
348 return operationResult;
352 * This method submits an HTTP PUT request against the supplied URL.
354 * @param url - The REST endpoint to submit the PUT request to.
355 * @param payload - the payload to send to the supplied URL
356 * @param headers - The headers that should be passed in the request
357 * @param contentType - The content type of the payload
358 * @param responseType - The expected format of the response.
360 * @return The result of the PUT request.
362 public OperationResult put(String url, String payload, Map<String, List<String>> headers,
363 MediaType contentType, MediaType responseType) {
364 return processRequest(putOp, url, payload, headers, contentType, responseType);
368 * This method submits an HTTP POST request against the supplied URL.
370 * @param url - The REST endpoint to submit the POST request to.
371 * @param payload - the payload to send to the supplied URL
372 * @param headers - The headers that should be passed in the request
373 * @param contentType - The content type of the payload
374 * @param responseType - The expected format of the response.
376 * @return The result of the POST request.
378 public OperationResult post(String url, String payload, Map<String, List<String>> headers,
379 MediaType contentType, MediaType responseType) {
380 return processRequest(postOp, url, payload, headers, contentType, responseType);
384 * This method submits an HTTP GET request against the supplied URL.
386 * @param url - The REST endpoint to submit the GET request to.
387 * @param headers - The headers that should be passed in the request
388 * @param responseType - The expected format of the response.
390 * @return The result of the GET request.
392 public OperationResult get(String url, Map<String, List<String>> headers,
393 MediaType responseType) {
394 return processRequest(getOp, url, null, headers, null, responseType);
398 * This method submits an HTTP GET request against the supplied URL.
399 * This variant of the method will perform a requested number of retries in the event that the
400 * first request is unsuccessful.
402 * @param url - The REST endpoint to submit the GET request to.
403 * @param headers - The headers that should be passed in the request
404 * @param responseType - The expected format of the response.
405 * @param numRetries - The number of times to try resubmitting the request in the event of a
408 * @return The result of the GET request.
410 public OperationResult get(String url, Map<String, List<String>> headers, MediaType responseType,
412 return processRequest(getOp, url, null, headers, null, responseType, numRetries);
416 * This method submits an HTTP DELETE request against the supplied URL.
418 * @param url - The REST endpoint to submit the DELETE request to.
419 * @param headers - The headers that should be passed in the request
420 * @param responseType - The expected format of the response.
422 * @return The result of the DELETE request.
424 public OperationResult delete(String url, Map<String, List<String>> headers,
425 MediaType responseType) {
426 return processRequest(deleteOp, url, null, headers, null, responseType);
430 * This method does a health check ("ping") against the supplied URL.
432 * @param url - The REST endpoint to attempt a health check.
433 * @param srcAppName - The name of the application using this client.
434 * @param destAppName - The name of the destination app.
436 * @return A boolean value. True if connection attempt was successful, false otherwise.
439 public boolean healthCheck(String url, String srcAppName, String destAppName) {
440 return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE);
445 * This method does a health check ("ping") against the supplied URL.
447 * @param url - The REST endpoint to attempt a health check.
448 * @param srcAppName - The name of the application using this client.
449 * @param destAppName - The name of the destination app.
450 * @param responseType - The response type.
452 * @return A boolean value. True if connection attempt was successful, false otherwise.
455 public boolean healthCheck(String url, String srcAppName, String destAppName,
456 MediaType responseType) {
457 MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
458 headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName}));
459 headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()}));
462 logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url);
463 OperationResult result = get(url, headers, responseType);
465 if (result != null && result.getFailureCause() == null) {
466 logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url);
469 logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url,
470 result.getFailureCause());
473 } catch (Exception e) {
474 logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage());
480 * This method constructs a client request builder that can be used for submitting REST requests
481 * to the supplied URL endpoint.
483 * @param client - The REST client we will be using to talk to the server.
484 * @param url - The URL endpoint that our request will be submitted to.
485 * @param headers - The headers that should be passed in the request
486 * @param contentType - the content type of the payload
487 * @param responseType - The expected format of the response.
489 * @return A client request builder.
491 private Builder getClientBuilder(Client client, String url, String payload,
492 Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
494 WebResource resource = client.resource(url);
495 Builder builder = null;
497 builder = resource.accept(responseType);
499 if (contentType != null) {
500 builder.type(contentType);
503 if (payload != null) {
504 builder.entity(payload);
507 if (headers != null) {
508 for (Entry<String, List<String>> header : headers.entrySet()) {
509 builder.header(header.getKey(), header.getValue());
516 private void debugRequest(String url, String payload, Map<String, List<String>> headers,
517 MediaType responseType) {
518 if (logger.isDebugEnabled()) {
519 StringBuilder debugRequest = new StringBuilder("REQUEST:\n");
520 debugRequest.append("URL: ").append(url).append("\n");
521 debugRequest.append("Payload: ").append(payload).append("\n");
522 debugRequest.append("Response Type: ").append(responseType).append("\n");
523 if (headers != null) {
524 debugRequest.append("Headers: ");
525 for (Entry<String, List<String>> header : headers.entrySet()) {
526 debugRequest.append("\n\t").append(header.getKey()).append(":");
527 for (String headerEntry : header.getValue()) {
528 debugRequest.append("\"").append(headerEntry).append("\" ");
532 logger.debug(debugRequest.toString());
536 private void debugResponse(OperationResult operationResult,
537 MultivaluedMap<String, String> headers) {
538 if (logger.isDebugEnabled()) {
539 StringBuilder debugResponse = new StringBuilder("RESPONSE:\n");
540 debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n");
541 debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause())
543 debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n");
544 if (headers != null) {
545 debugResponse.append("Headers: ");
546 for (Entry<String, List<String>> header : headers.entrySet()) {
547 debugResponse.append("\n\t").append(header.getKey()).append(":");
548 for (String headerEntry : header.getValue()) {
549 debugResponse.append("\"").append(headerEntry).append("\" ");
553 logger.debug(debugResponse.toString());
559 * This method creates an instance of the low level REST client to use for communicating with the
560 * AAI, if one has not already been created, otherwise it returns the already created instance.
562 * @return A {@link Client} instance.
564 private synchronized Client getClient() throws Exception {
566 if (restClient == null) {
568 if (logger.isDebugEnabled()) {
569 logger.debug("Instantiating REST client with following parameters:");
571 " validate server hostname = " + clientBuilder.isValidateServerHostname());
573 " validate server certificate chain = " + clientBuilder.isValidateServerCertChain());
575 " client certificate filename = " + clientBuilder.getClientCertFileName());
577 " client certificate password = " + clientBuilder.getClientCertPassword());
579 " trust store filename = " + clientBuilder.getTruststoreFilename());
580 logger.debug(" connection timeout = "
581 + clientBuilder.getConnectTimeoutInMs() + " ms");
583 " read timeout = " + clientBuilder.getReadTimeoutInMs() + " ms");
586 restClient = clientBuilder.getClient();
594 * This method populates the fields of an {@link OperationResult} instance based on the contents
595 * of a {@link ClientResponse} received in response to a REST request.
597 private void populateOperationResult(ClientResponse response, OperationResult opResult) {
599 // If we got back a NULL response, then just produce a generic
600 // error code and result indicating this.
601 if (response == null) {
602 opResult.setResultCode(500);
603 opResult.setFailureCause("Client response was null");
607 int statusCode = response.getStatus();
608 String payload = response.getEntity(String.class);
610 opResult.setResultCode(statusCode);
612 if ((statusCode < 200) || (statusCode > 299)) {
613 opResult.setFailureCause(payload);
615 opResult.setResult(payload);
618 opResult.setHeaders(response.getHeaders());
621 private class GetRestOperation implements RestOperation {
622 public ClientResponse processOperation(Builder builder) {
623 return builder.get(ClientResponse.class);
626 public RequestType getRequestType() {
627 return RequestType.GET;
631 private class PutRestOperation implements RestOperation {
632 public ClientResponse processOperation(Builder builder) {
633 return builder.put(ClientResponse.class);
636 public RequestType getRequestType() {
637 return RequestType.PUT;
641 private class PostRestOperation implements RestOperation {
642 public ClientResponse processOperation(Builder builder) {
643 return builder.post(ClientResponse.class);
646 public RequestType getRequestType() {
647 return RequestType.POST;
651 private class DeleteRestOperation implements RestOperation {
652 public ClientResponse processOperation(Builder builder) {
653 return builder.delete(ClientResponse.class);
656 public RequestType getRequestType() {
657 return RequestType.DELETE;
662 * Interface used wrap a Jersey REST call using a functional interface.
664 private interface RestOperation {
667 * Method used to wrap the functionality of making a REST call out to the endpoint.
669 * @param builder the Jersey builder used to make the request
670 * @return the response from the REST endpoint
672 public ClientResponse processOperation(Builder builder);
675 * Returns the REST request type.
677 public RequestType getRequestType();
680 * The supported REST request types.
682 public enum RequestType {
683 GET, PUT, POST, DELETE;