2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * Copyright © 2017-2018 Amdocs
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
21 package org.onap.aai.restclient.client;
23 import java.io.ByteArrayOutputStream;
24 import java.text.SimpleDateFormat;
25 import java.util.Arrays;
26 import java.util.List;
28 import java.util.Map.Entry;
29 import java.util.UUID;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.MultivaluedHashMap;
36 import javax.ws.rs.core.Response;
38 import org.onap.aai.restclient.enums.RestAuthenticationMode;
39 import org.onap.aai.restclient.logging.RestClientMsgs;
40 import org.onap.aai.restclient.rest.RestClientBuilder;
41 import org.onap.aai.cl.api.LogFields;
42 import org.onap.aai.cl.api.LogLine;
43 import org.onap.aai.cl.api.Logger;
44 import org.onap.aai.cl.eelf.LoggerFactory;
45 import org.onap.aai.cl.mdc.MdcContext;
46 import org.onap.aai.cl.mdc.MdcOverride;
48 import com.sun.jersey.api.client.Client;
49 import com.sun.jersey.api.client.ClientResponse;
50 import com.sun.jersey.api.client.WebResource;
51 import com.sun.jersey.api.client.WebResource.Builder;
52 import com.sun.jersey.core.util.MultivaluedMapImpl;
56 * This class provides a general client implementation that micro services can use for communicating
57 * with the endpoints via their exposed REST interfaces.
61 public class RestClient {
64 * This is a generic builder that is used for constructing the REST client that we will use to
65 * communicate with the REST endpoint.
67 private RestClientBuilder clientBuilder;
69 private final ConcurrentMap<String,InitializedClient> CLIENT_CACHE = new ConcurrentHashMap<>();
70 private static final String REST_CLIENT_INSTANCE = "REST_CLIENT_INSTANCE";
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();
92 /** Reusable function call for HEAD REST operations. */
93 private final RestOperation headOp = new HeadRestOperation();
95 /** Reusable function call for PATCH REST operations. */
96 private final RestOperation patchOp = new PatchRestOperation();
100 * Creates a new instance of the {@link RestClient}.
102 public RestClient() {
104 clientBuilder = new RestClientBuilder();
110 * Creates a new instance of the {@link RestClient} using the supplied {@link RestClientBuilder}.
112 * @param rcBuilder - The REST client builder that this instance of the {@link RestClient} should
115 public RestClient(RestClientBuilder rcBuilder) {
116 clientBuilder = rcBuilder;
119 public RestClient authenticationMode(RestAuthenticationMode mode) {
120 logger.debug("Set rest authentication mode= " + mode);
121 clientBuilder.setAuthenticationMode(mode);
125 public RestClient basicAuthUsername(String username) {
126 logger.debug("Set SSL BasicAuth username = " + username);
127 clientBuilder.setBasicAuthUsername(username);
131 public RestClient basicAuthPassword(String password) {
133 * purposely not logging out the password, I guess we could obfuscate it if we really want to
136 clientBuilder.setBasicAuthPassword(password);
142 * Sets the flag to indicate whether or not validation should be performed against the host name
143 * of the server we are trying to communicate with.
145 * @parameter validate - Set to true to enable validation, false to disable
147 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
149 public RestClient validateServerHostname(boolean validate) {
150 logger.debug("Set validate server hostname = " + validate);
151 clientBuilder.setValidateServerHostname(validate);
157 * Sets the flag to indicate whether or not validation should be performed against the certificate
160 * @parameter validate - Set to true to enable validation, false to disable.
162 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
164 public RestClient validateServerCertChain(boolean validate) {
165 logger.debug("Set validate server certificate chain = " + validate);
166 clientBuilder.setValidateServerCertChain(validate);
172 * Assigns the client certificate file to use.
174 * @param filename - The name of the certificate file.
176 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
178 public RestClient clientCertFile(String filename) {
179 logger.debug("Set client certificate filename = " + filename);
180 clientBuilder.setClientCertFileName(filename);
186 * Assigns the client certificate password to use.
188 * @param password - The certificate password.
190 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
192 public RestClient clientCertPassword(String password) {
193 clientBuilder.setClientCertPassword(password);
199 * Assigns the name of the trust store file to use.
201 * @param filename - the name of the trust store file.
203 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
205 public RestClient trustStore(String filename) {
206 logger.debug("Set trust store filename = " + filename);
207 clientBuilder.setTruststoreFilename(filename);
213 * Assigns the connection timeout (in ms) to use when connecting to the target server.
215 * @param timeout - The length of time to wait in ms before timing out.
217 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
219 public RestClient connectTimeoutMs(int timeout) {
220 logger.debug("Set connection timeout = " + timeout + " ms");
221 clientBuilder.setConnectTimeoutInMs(timeout);
227 * Assigns the read timeout (in ms) to use when communicating with the target server.
229 * @param timeout The read timeout in milliseconds.
231 * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
233 public RestClient readTimeoutMs(int timeout) {
234 logger.debug("Set read timeout = " + timeout + " ms");
235 clientBuilder.setReadTimeoutInMs(timeout);
240 * Configures the client for a specific SSL protocol
242 * @param sslProtocol - protocol string constant such as TLS, TLSv1, TLSv1.1, TLSv1.2
244 * @return The AAIRESTClient instance.
246 public RestClient sslProtocol(String sslProtocol) {
247 logger.debug("Set sslProtocol = " + sslProtocol);
248 clientBuilder.setSslProtocol(sslProtocol);
252 private boolean shouldRetry(OperationResult operationResult) {
254 if (operationResult == null) {
258 int resultCode = operationResult.getResultCode();
260 if (resultCode == 200) {
264 if (resultCode == 404) {
273 * This method operates on a REST endpoint by submitting an HTTP operation request against the
275 * This variant of the method will perform a requested number of retries in the event that the
276 * first request is unsuccessful.
278 * @param operation - the REST operation type to send to the url
279 * @param url - The REST endpoint to submit the REST request to.
280 * @param payload - They payload to provide in the REST request, if applicable
281 * @param headers - The headers that should be passed in the request
282 * @param contentType - The content type of the payload
283 * @param responseType - The expected format of the response.
285 * @return The result of the REST request.
287 protected OperationResult processRequest(RestOperation operation, String url, String payload,
288 Map<String, List<String>> headers, MediaType contentType, MediaType responseType,
292 OperationResult result = null;
294 long startTimeInMs = System.currentTimeMillis();
295 for (int retryCount = 0; retryCount < numRetries; retryCount++) {
297 logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, operation.getRequestType().toString(),
298 url, Integer.toString(retryCount + 1));
300 // Submit our query to the AAI.
301 result = processRequest(operation, url, payload, headers, contentType, responseType);
303 // If the submission was successful then we're done.
305 if (!shouldRetry(result)) {
307 logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES, operation.getRequestType().toString(),url,
308 Long.toString(System.currentTimeMillis() - startTimeInMs),
309 Integer.toString(retryCount));
311 result.setNumRetries(retryCount);
316 // Our submission was unsuccessful...
318 // Sleep between re-tries to be nice to the target system.
321 } catch (InterruptedException e) {
322 logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage());
323 Thread.currentThread().interrupt();
328 // If we've gotten this far, then we failed all of our retries.
329 if (result == null) {
330 result = new OperationResult();
333 result.setNumRetries(numRetries);
334 result.setResultCode(504);
335 result.setFailureCause("Failed to get a successful result after multiple retries to target server.");
342 * This method operates on a REST endpoint by submitting an HTTP operation request against the
345 * @param operation - the REST operation type to send to the url
346 * @param url - The REST endpoint to submit the REST request to.
347 * @param payload - They payload to provide in the REST request, if applicable
348 * @param headers - The headers that should be passed in the request
349 * @param contentType - The content type of the payload
350 * @param responseType - The expected format of the response.
352 * @return The result of the REST request.
354 protected OperationResult processRequest(RestOperation operation, String url, String payload,
355 Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
357 ClientResponse clientResponse = null;
358 OperationResult operationResult = new OperationResult();
359 ByteArrayOutputStream baos = new ByteArrayOutputStream();
361 String requestType = operation.getRequestType().name();
363 // Grab the current time so that we can log how long the
364 // query took once we are done.
365 long startTimeInMs = System.currentTimeMillis();
366 MdcOverride override = new MdcOverride();
367 override.addAttribute(MdcContext.MDC_START_TIME, formatter.format(startTimeInMs));
369 logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url);
373 // Get a REST client instance for our request.
374 Client client = getClient();
376 // Debug log the request
377 debugRequest(url, payload, headers, responseType);
379 // Get a client request builder, and submit our GET request.
380 Builder builder = getClientBuilder(client, url, payload, headers, contentType, responseType);
381 clientResponse = operation.processOperation(builder);
383 populateOperationResult(clientResponse, operationResult);
385 // Debug log the response
386 if (clientResponse != null) {
387 debugResponse(operationResult, clientResponse.getHeaders());
390 } catch (Exception ex) {
392 logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage());
393 operationResult.setResultCode(500);
394 operationResult.setFailureCause(
395 "Error during GET operation to AAI with message = " + ex.getLocalizedMessage());
399 if (logger.isDebugEnabled()) {
400 logger.debug(baos.toString());
403 // Not every valid response code is actually represented by the Response.Status
404 // object, so we need to guard against missing codes, otherwise we throw null
405 // pointer exceptions when we try to generate our metrics logs...
406 Response.Status responseStatus =
407 Response.Status.fromStatusCode(operationResult.getResultCode());
408 String responseStatusCodeString = "";
409 if (responseStatus != null) {
410 responseStatusCodeString = responseStatus.toString();
413 metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME,
414 new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString)
415 .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode())
416 .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()),
417 override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url);
418 logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType,
419 Long.toString(System.currentTimeMillis() - startTimeInMs), url);
420 logger.info(RestClientMsgs.HTTP_RESPONSE, url,
421 operationResult.getResultCode() + " " + responseStatusCodeString);
424 return operationResult;
428 * This method submits an HTTP PUT request against the supplied URL.
430 * @param url - The REST endpoint to submit the PUT request to.
431 * @param payload - the payload to send to the supplied URL
432 * @param headers - The headers that should be passed in the request
433 * @param contentType - The content type of the payload
434 * @param responseType - The expected format of the response.
436 * @return The result of the PUT request.
438 public OperationResult put(String url, String payload, Map<String, List<String>> headers,
439 MediaType contentType, MediaType responseType) {
440 return processRequest(putOp, url, payload, headers, contentType, responseType);
444 * This method submits an HTTP POST request against the supplied URL.
446 * @param url - The REST endpoint to submit the POST request to.
447 * @param payload - the payload to send to the supplied URL
448 * @param headers - The headers that should be passed in the request
449 * @param contentType - The content type of the payload
450 * @param responseType - The expected format of the response.
452 * @return The result of the POST request.
454 public OperationResult post(String url, String payload, Map<String, List<String>> headers,
455 MediaType contentType, MediaType responseType) {
456 return processRequest(postOp, url, payload, headers, contentType, responseType);
460 * This method submits an HTTP POST request against the supplied URL, and emulates a PATCH
461 * operation by setting a special header value
463 * @param url - The REST endpoint to submit the POST request to.
464 * @param payload - the payload to send to the supplied URL
465 * @param headers - The headers that should be passed in the request
466 * @param contentType - The content type of the payload
467 * @param responseType - The expected format of the response.
469 * @return The result of the POST request.
471 public OperationResult patch(String url, String payload, Map<String, List<String>> headers,
472 MediaType contentType, MediaType responseType) {
473 return processRequest(patchOp, url, payload, headers, contentType, responseType);
478 * This method submits an HTTP HEAD request against the supplied URL
480 * @param url - The REST endpoint to submit the POST request to.
481 * @param headers - The headers that should be passed in the request
482 * @param responseType - The expected format of the response.
484 * @return The result of the POST request.
486 public OperationResult head(String url, Map<String, List<String>> headers,
487 MediaType responseType) {
488 return processRequest(headOp, url, null, headers, null, responseType);
492 * This method submits an HTTP GET request against the supplied URL.
494 * @param url - The REST endpoint to submit the GET request to.
495 * @param headers - The headers that should be passed in the request
496 * @param responseType - The expected format of the response.
498 * @return The result of the GET request.
500 public OperationResult get(String url, Map<String, List<String>> headers,
501 MediaType responseType) {
502 return processRequest(getOp, url, null, headers, null, responseType);
506 * This method submits an HTTP GET request against the supplied URL.
507 * This variant of the method will perform a requested number of retries in the event that the
508 * first request is unsuccessful.
510 * @param url - The REST endpoint to submit the GET request to.
511 * @param headers - The headers that should be passed in the request
512 * @param responseType - The expected format of the response.
513 * @param numRetries - The number of times to try resubmitting the request in the event of a
516 * @return The result of the GET request.
518 public OperationResult get(String url, Map<String, List<String>> headers, MediaType responseType,
520 return processRequest(getOp, url, null, headers, null, responseType, numRetries);
524 * This method submits an HTTP DELETE request against the supplied URL.
526 * @param url - The REST endpoint to submit the DELETE request to.
527 * @param headers - The headers that should be passed in the request
528 * @param responseType - The expected format of the response.
530 * @return The result of the DELETE request.
532 public OperationResult delete(String url, Map<String, List<String>> headers,
533 MediaType responseType) {
534 return processRequest(deleteOp, url, null, headers, null, responseType);
538 * This method does a health check ("ping") against the supplied URL.
540 * @param url - The REST endpoint to attempt a health check.
541 * @param srcAppName - The name of the application using this client.
542 * @param destAppName - The name of the destination app.
544 * @return A boolean value. True if connection attempt was successful, false otherwise.
547 public boolean healthCheck(String url, String srcAppName, String destAppName) {
548 return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE);
553 * This method does a health check ("ping") against the supplied URL.
555 * @param url - The REST endpoint to attempt a health check.
556 * @param srcAppName - The name of the application using this client.
557 * @param destAppName - The name of the destination app.
558 * @param responseType - The response type.
560 * @return A boolean value. True if connection attempt was successful, false otherwise.
563 public boolean healthCheck(String url, String srcAppName, String destAppName,
564 MediaType responseType) {
565 MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
566 headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName}));
567 headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()}));
570 logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url);
571 OperationResult result = get(url, headers, responseType);
573 if (result != null && result.getFailureCause() == null) {
574 logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url);
577 logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, result != null ? result.getFailureCause()
581 } catch (Exception e) {
582 logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage());
588 * This method constructs a client request builder that can be used for submitting REST requests
589 * to the supplied URL endpoint.
591 * @param client - The REST client we will be using to talk to the server.
592 * @param url - The URL endpoint that our request will be submitted to.
593 * @param headers - The headers that should be passed in the request
594 * @param contentType - the content type of the payload
595 * @param responseType - The expected format of the response.
597 * @return A client request builder.
599 private Builder getClientBuilder(Client client, String url, String payload,
600 Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
602 WebResource resource = client.resource(url);
603 Builder builder = resource.accept(responseType);
605 if (contentType != null) {
606 builder.type(contentType);
609 if (payload != null) {
610 builder.entity(payload);
613 if (headers != null) {
614 for (Entry<String, List<String>> header : headers.entrySet()) {
615 builder.header(header.getKey(), String.join(";",header.getValue()));
618 //Added additional check to prevent adding duplicate authorization header if client is already sending the authorization header
619 // AAI-1097 - For AAI calls when Rest authentication mode is selected as SSL_BASIC getting 403 error
620 if (clientBuilder.getAuthenticationMode() == RestAuthenticationMode.SSL_BASIC && headers.get(Headers.AUTHORIZATION) == null) {
621 builder = builder.header(Headers.AUTHORIZATION,
622 clientBuilder.getBasicAuthenticationCredentials());
630 private void debugRequest(String url, String payload, Map<String, List<String>> headers,
631 MediaType responseType) {
632 if (!logger.isDebugEnabled()) {
636 StringBuilder debugRequest = new StringBuilder("REQUEST:\n");
637 debugRequest.append("URL: ").append(url).append("\n");
638 debugRequest.append("Payload: ").append(payload).append("\n");
639 debugRequest.append("Response Type: ").append(responseType).append("\n");
641 if (headers == null) {
642 logger.debug(debugRequest.toString());
646 debugRequest.append("Headers: ");
647 for (Entry<String, List<String>> header : headers.entrySet()) {
648 debugRequest.append("\n\t").append(header.getKey()).append(":");
649 for (String headerEntry : header.getValue()) {
650 debugRequest.append("\"").append(headerEntry).append("\" ");
654 logger.debug(debugRequest.toString());
658 private void debugResponse(OperationResult operationResult,
659 MultivaluedMap<String, String> headers) {
661 if (!logger.isDebugEnabled()) {
665 StringBuilder debugResponse = new StringBuilder("RESPONSE:\n");
666 debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n");
667 debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause()).append("\n");
668 debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n");
670 if (headers == null) {
671 logger.debug(debugResponse.toString());
675 debugResponse.append("Headers: ");
676 for (Entry<String, List<String>> header : headers.entrySet()) {
677 debugResponse.append("\n\t").append(header.getKey()).append(":");
678 for (String headerEntry : header.getValue()) {
679 debugResponse.append("\"").append(headerEntry).append("\" ");
683 logger.debug(debugResponse.toString());
687 * This method creates an instance of the low level REST client to use for communicating with the
688 * AAI, if one has not already been created, otherwise it returns the already created instance.
690 * @return A {@link Client} instance.
692 protected Client getClient() throws Exception {
695 * Attempting a new way of doing non-blocking thread-safe lazy-initialization by using Java 1.8
696 * computeIfAbsent functionality. A null value will not be stored, but once a valid mapping has
697 * been established, then the same value will be returned.
699 * One awkwardness of the computeIfAbsent is the lack of support for thrown exceptions, which
700 * required a bit of hoop jumping to preserve the original exception for the purpose of
701 * maintaining the pre-existing this API signature.
704 final InitializedClient clientInstance =
705 CLIENT_CACHE.computeIfAbsent(REST_CLIENT_INSTANCE, k -> loggedClientInitialization());
707 if (clientInstance.getCaughtException() != null) {
708 throw new InstantiationException(clientInstance.getCaughtException().getMessage());
711 return clientInstance.getClient();
716 * This method will only be called if computerIfAbsent is true. The return value is null, then the result is not
719 * @return a new client instance or null
721 private InitializedClient loggedClientInitialization() {
723 if (logger.isDebugEnabled()) {
724 logger.debug("Instantiating REST client with following parameters:");
725 logger.debug(clientBuilder.toString());
728 InitializedClient initClient = new InitializedClient();
731 initClient.setClient(clientBuilder.getClient());
732 } catch ( Exception error) {
733 initClient.setCaughtException(error);
742 * This method populates the fields of an {@link OperationResult} instance based on the contents
743 * of a {@link ClientResponse} received in response to a REST request.
745 private void populateOperationResult(ClientResponse response, OperationResult opResult) {
747 // If we got back a NULL response, then just produce a generic
748 // error code and result indicating this.
749 if (response == null) {
750 opResult.setResultCode(500);
751 opResult.setFailureCause("Client response was null");
755 int statusCode = response.getStatus();
756 opResult.setResultCode(statusCode);
758 if (opResult.wasSuccessful()) {
759 if (statusCode != Response.Status.NO_CONTENT.getStatusCode()) {
760 opResult.setResult(response.getEntity(String.class));
763 opResult.setFailureCause(response.getEntity(String.class));
766 opResult.setHeaders(response.getHeaders());
769 private class GetRestOperation implements RestOperation {
770 public ClientResponse processOperation(Builder builder) {
771 return builder.get(ClientResponse.class);
774 public RequestType getRequestType() {
775 return RequestType.GET;
779 private class PutRestOperation implements RestOperation {
780 public ClientResponse processOperation(Builder builder) {
781 return builder.put(ClientResponse.class);
784 public RequestType getRequestType() {
785 return RequestType.PUT;
789 private class PostRestOperation implements RestOperation {
790 public ClientResponse processOperation(Builder builder) {
791 return builder.post(ClientResponse.class);
794 public RequestType getRequestType() {
795 return RequestType.POST;
799 private class DeleteRestOperation implements RestOperation {
800 public ClientResponse processOperation(Builder builder) {
801 return builder.delete(ClientResponse.class);
804 public RequestType getRequestType() {
805 return RequestType.DELETE;
809 private class HeadRestOperation implements RestOperation {
810 public ClientResponse processOperation(Builder builder) {
811 return builder.head();
814 public RequestType getRequestType() {
815 return RequestType.HEAD;
819 private class PatchRestOperation implements RestOperation {
822 * Technically there is no standarized PATCH operation for the
823 * jersey client, but we can use the method-override approach
826 public ClientResponse processOperation(Builder builder) {
827 builder = builder.header("X-HTTP-Method-Override", "PATCH");
828 return builder.post(ClientResponse.class);
831 public RequestType getRequestType() {
832 return RequestType.PATCH;
838 * Interface used wrap a Jersey REST call using a functional interface.
840 private interface RestOperation {
843 * Method used to wrap the functionality of making a REST call out to the endpoint.
845 * @param builder the Jersey builder used to make the request
846 * @return the response from the REST endpoint
848 public ClientResponse processOperation(Builder builder);
851 * Returns the REST request type.
853 public RequestType getRequestType();
856 * The supported REST request types.
858 public enum RequestType {
859 GET, PUT, POST, DELETE, PATCH, HEAD
864 * An entity to encapsulate an expected result and a potential failure cause when returning from a
865 * functional interface during the computeIfAbsent call.
867 private class InitializedClient {
868 private Client client;
869 private Throwable caughtException;
871 public InitializedClient() {
873 caughtException = null;
876 public Client getClient() {
879 public void setClient(Client client) {
880 this.client = client;
882 public Throwable getCaughtException() {
883 return caughtException;
885 public void setCaughtException(Throwable caughtException) {
886 this.caughtException = caughtException;