Fixed null pointer Blocker/Critical sonar issues
[aai/rest-client.git] / src / main / java / org / onap / aai / restclient / client / RestClient.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.onap.aai.restclient.client;
24
25 import java.io.ByteArrayOutputStream;
26 import java.text.SimpleDateFormat;
27 import java.util.Arrays;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.UUID;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ConcurrentMap;
34
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.MultivaluedMap;
37 import javax.ws.rs.core.Response;
38
39 import org.onap.aai.restclient.enums.RestAuthenticationMode;
40 import org.onap.aai.restclient.logging.RestClientMsgs;
41 import org.onap.aai.restclient.rest.RestClientBuilder;
42 import org.onap.aai.cl.api.LogFields;
43 import org.onap.aai.cl.api.LogLine;
44 import org.onap.aai.cl.api.Logger;
45 import org.onap.aai.cl.eelf.LoggerFactory;
46 import org.onap.aai.cl.mdc.MdcContext;
47 import org.onap.aai.cl.mdc.MdcOverride;
48
49 import com.sun.jersey.api.client.Client;
50 import com.sun.jersey.api.client.ClientResponse;
51 import com.sun.jersey.api.client.WebResource;
52 import com.sun.jersey.api.client.WebResource.Builder;
53 import com.sun.jersey.core.util.MultivaluedMapImpl;
54
55
56 /**
57  * This class provides a general client implementation that micro services can use for communicating
58  * with the endpoints via their exposed REST interfaces.
59  * 
60  */
61
62 public class RestClient {
63
64   /**
65    * This is a generic builder that is used for constructing the REST client that we will use to
66    * communicate with the REST endpoint.
67    */
68   private RestClientBuilder clientBuilder;
69   
70   private final ConcurrentMap<String,InitializedClient> CLIENT_CACHE = new ConcurrentHashMap<>();
71   private static final String REST_CLIENT_INSTANCE = "REST_CLIENT_INSTANCE";
72
73   /** Standard logger for producing log statements. */
74   private Logger logger = LoggerFactory.getInstance().getLogger("AAIRESTClient");
75
76   /** Standard logger for producing metric statements. */
77   private Logger metricsLogger = LoggerFactory.getInstance().getMetricsLogger("AAIRESTClient");
78
79   private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
80
81   /** Reusable function call for GET REST operations. */
82   private final RestOperation getOp = new GetRestOperation();
83
84   /** Reusable function call for PUT REST operations. */
85   private final RestOperation putOp = new PutRestOperation();
86
87   /** Reusable function call for POST REST operations. */
88   private final RestOperation postOp = new PostRestOperation();
89
90   /** Reusable function call for DELETE REST operations. */
91   private final RestOperation deleteOp = new DeleteRestOperation();
92
93   /** Reusable function call for HEAD REST operations. */
94   private final RestOperation headOp = new HeadRestOperation();
95
96   /** Reusable function call for PATCH REST operations. */
97   private final RestOperation patchOp = new PatchRestOperation();
98   
99   
100   /**
101    * Creates a new instance of the {@link RestClient}.
102    */
103   public RestClient() {
104     
105     clientBuilder = new RestClientBuilder();
106   
107   }
108
109
110   /**
111    * Creates a new instance of the {@link RestClient} using the supplied {@link RestClientBuilder}.
112    *
113    * @param rcBuilder - The REST client builder that this instance of the {@link RestClient} should
114    *        use.
115    */
116   public RestClient(RestClientBuilder rcBuilder) {
117     clientBuilder = rcBuilder;
118   }
119   
120   public RestClient authenticationMode(RestAuthenticationMode mode) {
121     logger.debug("Set rest authentication mode= " + mode);
122     clientBuilder.setAuthenticationMode(mode);
123     return this;
124   }
125   
126   public RestClient basicAuthUsername(String username) {
127     logger.debug("Set SSL BasicAuth username = " + username);
128     clientBuilder.setBasicAuthUsername(username);
129     return this;
130   }
131   
132   public RestClient basicAuthPassword(String password) {
133     /*
134      * purposely not logging out the password, I guess we could obfuscate it if we really want to
135      * see it in the logs
136      */
137     clientBuilder.setBasicAuthPassword(password);
138     return this;
139   }
140
141
142   /**
143    * Sets the flag to indicate whether or not validation should be performed against the host name
144    * of the server we are trying to communicate with.
145    *
146    * @parameter validate - Set to true to enable validation, false to disable
147    *
148    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
149    */
150   public RestClient validateServerHostname(boolean validate) {
151     logger.debug("Set validate server hostname = " + validate);
152     clientBuilder.setValidateServerHostname(validate);
153     return this;
154   }
155
156
157   /**
158    * Sets the flag to indicate whether or not validation should be performed against the certificate
159    * chain.
160    *
161    * @parameter validate - Set to true to enable validation, false to disable.
162    *
163    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
164    */
165   public RestClient validateServerCertChain(boolean validate) {
166     logger.debug("Set validate server certificate chain = " + validate);
167     clientBuilder.setValidateServerCertChain(validate);
168     return this;
169   }
170
171
172   /**
173    * Assigns the client certificate file to use.
174    *
175    * @param filename - The name of the certificate file.
176    *
177    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
178    */
179   public RestClient clientCertFile(String filename) {
180     logger.debug("Set client certificate filename = " + filename);
181     clientBuilder.setClientCertFileName(filename);
182     return this;
183   }
184
185
186   /**
187    * Assigns the client certificate password to use.
188    *
189    * @param password - The certificate password.
190    *
191    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
192    */
193   public RestClient clientCertPassword(String password) {
194     clientBuilder.setClientCertPassword(password);
195     return this;
196   }
197
198
199   /**
200    * Assigns the name of the trust store file to use.
201    *
202    * @param filename - the name of the trust store file.
203    *
204    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
205    */
206   public RestClient trustStore(String filename) {
207     logger.debug("Set trust store filename = " + filename);
208     clientBuilder.setTruststoreFilename(filename);
209     return this;
210   }
211
212
213   /**
214    * Assigns the connection timeout (in ms) to use when connecting to the target server.
215    *
216    * @param timeout - The length of time to wait in ms before timing out.
217    *
218    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
219    */
220   public RestClient connectTimeoutMs(int timeout) {
221     logger.debug("Set connection timeout = " + timeout + " ms");
222     clientBuilder.setConnectTimeoutInMs(timeout);
223     return this;
224   }
225
226
227   /**
228    * Assigns the read timeout (in ms) to use when communicating with the target server.
229    *
230    * @param timeout The read timeout in milliseconds.
231    *
232    * @return The AAIRESTClient instance. This is useful for chaining parameter assignments.
233    */
234   public RestClient readTimeoutMs(int timeout) {
235     logger.debug("Set read timeout = " + timeout + " ms");
236     clientBuilder.setReadTimeoutInMs(timeout);
237     return this;
238   }
239   
240   /**
241    * Configures the client for a specific SSL protocol
242    *
243    * @param sslProtocol - protocol string constant such as TLS, TLSv1, TLSv1.1, TLSv1.2
244    *
245    * @return The AAIRESTClient instance. 
246    */
247   public RestClient sslProtocol(String sslProtocol) {
248     logger.debug("Set sslProtocol = " + sslProtocol);
249     clientBuilder.setSslProtocol(sslProtocol);
250     return this;
251   }
252
253   private boolean shouldRetry(OperationResult operationResult) {
254
255     if (operationResult == null) {
256       return true;
257     }
258
259     int resultCode = operationResult.getResultCode();
260
261     if (resultCode == 200) {
262       return false;
263     }
264
265     if (resultCode == 404) {
266       return false;
267     }
268
269     return true;
270
271   }
272   
273   /**
274    * This method operates on a REST endpoint by submitting an HTTP operation request against the
275    * supplied URL.    
276    * This variant of the method will perform a requested number of retries in the event that the
277    * first request is unsuccessful.
278    *
279    * @param operation - the REST operation type to send to the url
280    * @param url - The REST endpoint to submit the REST request to.
281    * @param payload - They payload to provide in the REST request, if applicable
282    * @param headers - The headers that should be passed in the request
283    * @param contentType - The content type of the payload
284    * @param responseType - The expected format of the response.
285    * 
286    * @return The result of the REST request.
287    */
288   protected OperationResult processRequest(RestOperation operation, String url, String payload,
289       Map<String, List<String>> headers, MediaType contentType, MediaType responseType,
290       int numRetries) {
291
292
293     OperationResult result = null;
294
295     long startTimeInMs = System.currentTimeMillis();
296     for (int retryCount = 0; retryCount < numRetries; retryCount++) {
297
298       logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, operation.getRequestType().toString(),
299           url, Integer.toString(retryCount + 1));
300       
301       // Submit our query to the AAI.
302       result = processRequest(operation, url, payload, headers, contentType, responseType);
303
304       // If the submission was successful then we're done.
305       
306       if (!shouldRetry(result)) {
307         
308         logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES, operation.getRequestType().toString(),url,
309             Long.toString(System.currentTimeMillis() - startTimeInMs), 
310             Integer.toString(retryCount));
311         
312         result.setNumRetries(retryCount);
313         
314         return result;
315       }
316
317       // Our submission was unsuccessful...
318       try {
319         // Sleep between re-tries to be nice to the target system.
320         Thread.sleep(50);
321
322       } catch (InterruptedException e) {
323         logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage());
324         break;
325       }
326     }
327
328     // If we've gotten this far, then we failed all of our retries.
329     if (result == null) {
330         result = new OperationResult();
331     }
332
333     result.setNumRetries(numRetries);
334     result.setResultCode(504);
335     result.setFailureCause("Failed to get a successful result after multiple retries to target server.");
336
337
338     return result;
339   }
340
341   /**
342    * This method operates on a REST endpoint by submitting an HTTP operation request against the
343    * supplied URL.
344    *
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.
351    *
352    * @return The result of the REST request.
353    */
354   protected OperationResult processRequest(RestOperation operation, String url, String payload,
355       Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
356
357     ClientResponse clientResponse = null;
358     OperationResult operationResult = new OperationResult();
359     ByteArrayOutputStream baos = new ByteArrayOutputStream();
360
361     String requestType = operation.getRequestType().name();
362
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));
368
369     logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url);
370
371     try {
372
373       // Get a REST client instance for our request.
374       Client client = getClient();
375
376       // Debug log the request
377       debugRequest(url, payload, headers, responseType);
378
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);
382
383       populateOperationResult(clientResponse, operationResult);
384
385       // Debug log the response
386       debugResponse(operationResult, clientResponse.getHeaders());
387
388     } catch (Exception ex) {
389
390       logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage());
391       operationResult.setResultCode(500);
392       operationResult.setFailureCause(
393           "Error during GET operation to AAI with message = " + ex.getLocalizedMessage());
394
395     } finally {
396
397       if (logger.isDebugEnabled()) {
398         logger.debug(baos.toString());
399       }
400
401       // Not every valid response code is actually represented by the Response.Status
402       // object, so we need to guard against missing codes, otherwise we throw null
403       // pointer exceptions when we try to generate our metrics logs...
404       Response.Status responseStatus =
405           Response.Status.fromStatusCode(operationResult.getResultCode());
406       String responseStatusCodeString = "";
407       if (responseStatus != null) {
408         responseStatusCodeString = responseStatus.toString();
409       }
410
411       metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME,
412           new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString)
413               .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode())
414               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()),
415           override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url);
416       logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType,
417           Long.toString(System.currentTimeMillis() - startTimeInMs), url);
418       logger.info(RestClientMsgs.HTTP_RESPONSE, url,
419           operationResult.getResultCode() + " " + responseStatusCodeString);
420     }
421
422     return operationResult;
423   }
424
425   /**
426    * This method submits an HTTP PUT request against the supplied URL.
427    *
428    * @param url - The REST endpoint to submit the PUT request to.
429    * @param payload - the payload to send to the supplied URL
430    * @param headers - The headers that should be passed in the request
431    * @param contentType - The content type of the payload
432    * @param responseType - The expected format of the response.
433    *
434    * @return The result of the PUT request.
435    */
436   public OperationResult put(String url, String payload, Map<String, List<String>> headers,
437       MediaType contentType, MediaType responseType) {
438     return processRequest(putOp, url, payload, headers, contentType, responseType);
439   }
440
441   /**
442    * This method submits an HTTP POST request against the supplied URL.
443    *
444    * @param url - The REST endpoint to submit the POST request to.
445    * @param payload - the payload to send to the supplied URL
446    * @param headers - The headers that should be passed in the request
447    * @param contentType - The content type of the payload
448    * @param responseType - The expected format of the response.
449    *
450    * @return The result of the POST request.
451    */
452   public OperationResult post(String url, String payload, Map<String, List<String>> headers,
453       MediaType contentType, MediaType responseType) {
454     return processRequest(postOp, url, payload, headers, contentType, responseType);
455   }
456   
457   /**
458    * This method submits an HTTP POST request against the supplied URL, and emulates a PATCH
459    * operation by setting a special header value
460    *
461    * @param url - The REST endpoint to submit the POST request to.
462    * @param payload - the payload to send to the supplied URL
463    * @param headers - The headers that should be passed in the request
464    * @param contentType - The content type of the payload
465    * @param responseType - The expected format of the response.
466    *
467    * @return The result of the POST request.
468    */
469   public OperationResult patch(String url, String payload, Map<String, List<String>> headers,
470       MediaType contentType, MediaType responseType) {
471     return processRequest(patchOp, url, payload, headers, contentType, responseType);
472   }
473
474   
475   /**
476    * This method submits an HTTP HEAD request against the supplied URL
477    *
478    * @param url - The REST endpoint to submit the POST request to.
479    * @param headers - The headers that should be passed in the request
480    * @param responseType - The expected format of the response.
481    *
482    * @return The result of the POST request.
483    */
484   public OperationResult head(String url, Map<String, List<String>> headers,
485       MediaType responseType) {
486     return processRequest(headOp, url, null, headers, null, responseType);
487   }
488   
489   /**
490    * This method submits an HTTP GET request against the supplied URL.
491    *
492    * @param url - The REST endpoint to submit the GET request to.
493    * @param headers - The headers that should be passed in the request
494    * @param responseType - The expected format of the response.
495    *
496    * @return The result of the GET request.
497    */
498   public OperationResult get(String url, Map<String, List<String>> headers,
499       MediaType responseType) {
500     return processRequest(getOp, url, null, headers, null, responseType);
501   }
502
503   /**
504    * This method submits an HTTP GET request against the supplied URL. 
505    * This variant of the method will perform a requested number of retries in the event that the
506    * first request is unsuccessful.
507    * 
508    * @param url - The REST endpoint to submit the GET request to.
509    * @param headers - The headers that should be passed in the request
510    * @param responseType - The expected format of the response.
511    * @param numRetries - The number of times to try resubmitting the request in the event of a
512    *        failure.
513    * 
514    * @return The result of the GET request.
515    */
516   public OperationResult get(String url, Map<String, List<String>> headers, MediaType responseType,
517       int numRetries) {
518     return processRequest(getOp, url, null, headers, null, responseType, numRetries);
519   }
520
521   /**
522    * This method submits an HTTP DELETE request against the supplied URL.
523    *
524    * @param url - The REST endpoint to submit the DELETE request to.
525    * @param headers - The headers that should be passed in the request
526    * @param responseType - The expected format of the response.
527    *
528    * @return The result of the DELETE request.
529    */
530   public OperationResult delete(String url, Map<String, List<String>> headers,
531       MediaType responseType) {
532     return processRequest(deleteOp, url, null, headers, null, responseType);
533   }
534
535   /**
536    * This method does a health check ("ping") against the supplied URL.
537    *
538    * @param url - The REST endpoint to attempt a health check.
539    * @param srcAppName - The name of the application using this client.
540    * @param destAppName - The name of the destination app.
541    *
542    * @return A boolean value. True if connection attempt was successful, false otherwise.
543    *
544    */
545   public boolean healthCheck(String url, String srcAppName, String destAppName) {
546     return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE);
547
548   }
549
550   /**
551    * This method does a health check ("ping") against the supplied URL.
552    *
553    * @param url - The REST endpoint to attempt a health check.
554    * @param srcAppName - The name of the application using this client.
555    * @param destAppName - The name of the destination app.
556    * @param responseType - The response type.
557    *
558    * @return A boolean value. True if connection attempt was successful, false otherwise.
559    *
560    */
561   public boolean healthCheck(String url, String srcAppName, String destAppName,
562       MediaType responseType) {
563     MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
564     headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName}));
565     headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()}));
566
567     try {
568       logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url);
569       OperationResult result = get(url, headers, responseType);
570
571       if (result != null && result.getFailureCause() == null) {
572         logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url);
573         return true;
574       } else {
575         logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, result != null ? result.getFailureCause()
576                                                                                            : null);
577         return false;
578       }
579     } catch (Exception e) {
580       logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage());
581       return false;
582     }
583   }
584
585   /**
586    * This method constructs a client request builder that can be used for submitting REST requests
587    * to the supplied URL endpoint.
588    *
589    * @param client - The REST client we will be using to talk to the server.
590    * @param url - The URL endpoint that our request will be submitted to.
591    * @param headers - The headers that should be passed in the request
592    * @param contentType - the content type of the payload
593    * @param responseType - The expected format of the response.
594    *
595    * @return A client request builder.
596    */
597   private Builder getClientBuilder(Client client, String url, String payload,
598       Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
599
600     WebResource resource = client.resource(url);
601     Builder builder = resource.accept(responseType);
602
603     if (contentType != null) {
604       builder.type(contentType);
605     }
606
607     if (payload != null) {
608       builder.entity(payload);
609     }
610
611     if (headers != null) {
612       for (Entry<String, List<String>> header : headers.entrySet()) {
613         builder.header(header.getKey(), String.join(";",header.getValue()));
614       }
615       
616       if (clientBuilder.getAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
617         builder = builder.header(Headers.AUTHORIZATION,
618             clientBuilder.getBasicAuthenticationCredentials());
619       }
620       
621     }
622
623     return builder;
624   }
625
626   private void debugRequest(String url, String payload, Map<String, List<String>> headers,
627       MediaType responseType) {
628     if (logger.isDebugEnabled()) {
629       StringBuilder debugRequest = new StringBuilder("REQUEST:\n");
630       debugRequest.append("URL: ").append(url).append("\n");
631       debugRequest.append("Payload: ").append(payload).append("\n");
632       debugRequest.append("Response Type: ").append(responseType).append("\n");
633       if (headers != null) {
634         debugRequest.append("Headers: ");
635         for (Entry<String, List<String>> header : headers.entrySet()) {
636           debugRequest.append("\n\t").append(header.getKey()).append(":");
637           for (String headerEntry : header.getValue()) {
638             debugRequest.append("\"").append(headerEntry).append("\" ");
639           }
640         }
641       }
642       logger.debug(debugRequest.toString());
643     }
644   }
645
646   private void debugResponse(OperationResult operationResult,
647       MultivaluedMap<String, String> headers) {
648     if (logger.isDebugEnabled()) {
649       StringBuilder debugResponse = new StringBuilder("RESPONSE:\n");
650       debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n");
651       debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause())
652           .append("\n");
653       debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n");
654       if (headers != null) {
655         debugResponse.append("Headers: ");
656         for (Entry<String, List<String>> header : headers.entrySet()) {
657           debugResponse.append("\n\t").append(header.getKey()).append(":");
658           for (String headerEntry : header.getValue()) {
659             debugResponse.append("\"").append(headerEntry).append("\" ");
660           }
661         }
662       }
663       logger.debug(debugResponse.toString());
664     }
665   }
666
667   /**
668    * This method creates an instance of the low level REST client to use for communicating with the
669    * AAI, if one has not already been created, otherwise it returns the already created instance.
670    *
671    * @return A {@link Client} instance.
672    */
673   protected Client getClient() throws Exception {
674
675     /*
676      * Attempting a new way of doing non-blocking thread-safe lazy-initialization by using Java 1.8
677      * computeIfAbsent functionality. A null value will not be stored, but once a valid mapping has
678      * been established, then the same value will be returned.
679      * 
680      * One awkwardness of the computeIfAbsent is the lack of support for thrown exceptions, which
681      * required a bit of hoop jumping to preserve the original exception for the purpose of
682      * maintaining the pre-existing this API signature.
683      */
684  
685     final InitializedClient clientInstance =
686         CLIENT_CACHE.computeIfAbsent(REST_CLIENT_INSTANCE, k -> loggedClientInitialization());
687     
688     if (clientInstance.getCaughtException() != null) {
689       throw new InstantiationException(clientInstance.getCaughtException().getMessage());
690     }
691
692     return clientInstance.getClient();
693
694   }
695
696   /**
697    * This method will only be called if computerIfAbsent is true.  The return value is null, then the result is not
698    * stored in the map. 
699    * 
700    * @return a new client instance or null
701    */
702   private InitializedClient loggedClientInitialization() {
703
704     if (logger.isDebugEnabled()) {
705       logger.debug("Instantiating REST client with following parameters:");
706       logger.debug(clientBuilder.toString());
707     }
708     
709     InitializedClient initClient = new InitializedClient();
710     
711     try {
712       initClient.setClient(clientBuilder.getClient());
713     } catch ( Throwable error ) {
714       initClient.setCaughtException(error);
715     }
716     
717     return initClient;
718
719   }
720
721
722   /**
723    * This method populates the fields of an {@link OperationResult} instance based on the contents
724    * of a {@link ClientResponse} received in response to a REST request.
725    */
726   private void populateOperationResult(ClientResponse response, OperationResult opResult) {
727
728     // If we got back a NULL response, then just produce a generic
729     // error code and result indicating this.
730     if (response == null) {
731       opResult.setResultCode(500);
732       opResult.setFailureCause("Client response was null");
733       return;
734     }
735         
736     int statusCode = response.getStatus();
737     opResult.setResultCode(statusCode);
738
739     if (opResult.wasSuccessful()) {
740         if (statusCode != Response.Status.NO_CONTENT.getStatusCode()) {
741             opResult.setResult(response.getEntity(String.class));
742         }
743     } else {
744         opResult.setFailureCause(response.getEntity(String.class));
745     }
746
747     opResult.setHeaders(response.getHeaders());
748   }
749
750   private class GetRestOperation implements RestOperation {
751     public ClientResponse processOperation(Builder builder) {
752       return builder.get(ClientResponse.class);
753     }
754
755     public RequestType getRequestType() {
756       return RequestType.GET;
757     }
758   }
759
760   private class PutRestOperation implements RestOperation {
761     public ClientResponse processOperation(Builder builder) {
762       return builder.put(ClientResponse.class);
763     }
764
765     public RequestType getRequestType() {
766       return RequestType.PUT;
767     }
768   }
769
770   private class PostRestOperation implements RestOperation {
771     public ClientResponse processOperation(Builder builder) {
772       return builder.post(ClientResponse.class);
773     }
774
775     public RequestType getRequestType() {
776       return RequestType.POST;
777     }
778   }
779
780   private class DeleteRestOperation implements RestOperation {
781     public ClientResponse processOperation(Builder builder) {
782       return builder.delete(ClientResponse.class);
783     }
784
785     public RequestType getRequestType() {
786       return RequestType.DELETE;
787     }
788   }
789   
790   private class HeadRestOperation implements RestOperation {
791     public ClientResponse processOperation(Builder builder) {
792       return builder.head();
793     }
794
795     public RequestType getRequestType() {
796       return RequestType.HEAD;
797     }
798   }
799
800   private class PatchRestOperation implements RestOperation {
801
802     /**
803      * Technically there is no standarized PATCH operation for the 
804      * jersey client, but we can use the method-override approach 
805      * instead.
806      */
807     public ClientResponse processOperation(Builder builder) {
808       builder = builder.header("X-HTTP-Method-Override", "PATCH");
809       return builder.post(ClientResponse.class);
810     }
811
812     public RequestType getRequestType() {
813       return RequestType.PATCH;
814     }
815   }
816
817
818   /**
819    * Interface used wrap a Jersey REST call using a functional interface.
820    */
821   private interface RestOperation {
822
823     /**
824      * Method used to wrap the functionality of making a REST call out to the endpoint.
825      *
826      * @param builder the Jersey builder used to make the request
827      * @return the response from the REST endpoint
828      */
829     public ClientResponse processOperation(Builder builder);
830
831     /**
832      * Returns the REST request type.
833      */
834     public RequestType getRequestType();
835
836     /**
837      * The supported REST request types.
838      */
839     public enum RequestType {
840       GET, PUT, POST, DELETE, PATCH, HEAD
841     }
842   }
843   
844   /*
845    * An entity to encapsulate an expected result and a potential failure cause when returning from a
846    * functional interface during the computeIfAbsent call.
847    */
848   private class InitializedClient {
849     private Client client;
850     private Throwable caughtException;
851     
852     public InitializedClient() {
853       client = null;
854       caughtException = null;
855     }
856     
857     public Client getClient() {
858       return client;
859     }
860     public void setClient(Client client) {
861       this.client = client;
862     }
863     public Throwable getCaughtException() {
864       return caughtException;
865     }
866     public void setCaughtException(Throwable caughtException) {
867       this.caughtException = caughtException;
868     }
869   
870   }
871   
872 }