Changing the license and trademark
[aai/rest-client.git] / src / main / java / org / openecomp / 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.openecomp.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.openecomp.cl.api.LogFields;
40 import org.openecomp.cl.api.LogLine;
41 import org.openecomp.cl.api.Logger;
42 import org.openecomp.cl.eelf.LoggerFactory;
43 import org.openecomp.cl.mdc.MdcContext;
44 import org.openecomp.cl.mdc.MdcOverride;
45 import org.openecomp.restclient.enums.RestAuthenticationMode;
46 import org.openecomp.restclient.logging.RestClientMsgs;
47 import org.openecomp.restclient.rest.RestClientBuilder;
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<String,InitializedClient>();
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   private boolean shouldRetry(OperationResult operationResult) {
241
242     if (operationResult == null) {
243       return true;
244     }
245
246     int resultCode = operationResult.getResultCode();
247
248     if (resultCode == 200) {
249       return false;
250     }
251
252     if (resultCode == 404) {
253       return false;
254     }
255
256     return true;
257
258   }
259   
260   /**
261    * This method operates on a REST endpoint by submitting an HTTP operation request against the
262    * supplied URL.    
263    * This variant of the method will perform a requested number of retries in the event that the
264    * first request is unsuccessful.
265    *
266    * @param operation - the REST operation type to send to the url
267    * @param url - The REST endpoint to submit the REST request to.
268    * @param payload - They payload to provide in the REST request, if applicable
269    * @param headers - The headers that should be passed in the request
270    * @param contentType - The content type of the payload
271    * @param responseType - The expected format of the response.
272    * 
273    * @return The result of the REST request.
274    */
275   protected OperationResult processRequest(RestOperation operation, String url, String payload,
276       Map<String, List<String>> headers, MediaType contentType, MediaType responseType,
277       int numRetries) {
278
279
280     OperationResult result = null;
281
282     long startTimeInMs = System.currentTimeMillis();
283     for (int retryCount = 0; retryCount < numRetries; retryCount++) {
284
285       logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, operation.getRequestType().toString(),
286           url, Integer.toString(retryCount + 1));
287       
288       // Submit our query to the AAI.
289       result = processRequest(operation, url, payload, headers, contentType, responseType);
290
291       // If the submission was successful then we're done.
292       
293       if (!shouldRetry(result)) {
294         
295         logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES, operation.getRequestType().toString(),url,
296             Long.toString(System.currentTimeMillis() - startTimeInMs), 
297             Integer.toString(retryCount));
298         
299         result.setNumRetries(retryCount);
300         
301         return result;
302       }
303
304       // Our submission was unsuccessful...
305       try {
306         // Sleep between re-tries to be nice to the target system.
307         Thread.sleep(50);
308
309       } catch (InterruptedException e) {
310         logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage());
311         break;
312       }
313     }
314
315     // If we've gotten this far, then we failed all of our retries.
316     result.setNumRetries(numRetries);
317     result.setResultCode(504);
318     result.setFailureCause(
319         "Failed to get a successful result after multiple retries to target server.");
320
321     return result;
322   }
323
324   /**
325    * This method operates on a REST endpoint by submitting an HTTP operation request against the
326    * supplied URL.
327    *
328    * @param operation - the REST operation type to send to the url
329    * @param url - The REST endpoint to submit the REST request to.
330    * @param payload - They payload to provide in the REST request, if applicable
331    * @param headers - The headers that should be passed in the request
332    * @param contentType - The content type of the payload
333    * @param responseType - The expected format of the response.
334    *
335    * @return The result of the REST request.
336    */
337   protected OperationResult processRequest(RestOperation operation, String url, String payload,
338       Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
339
340     ClientResponse clientResponse = null;
341     OperationResult operationResult = new OperationResult();
342     ByteArrayOutputStream baos = new ByteArrayOutputStream();
343
344     String requestType = operation.getRequestType().name();
345
346     // Grab the current time so that we can log how long the
347     // query took once we are done.
348     long startTimeInMs = System.currentTimeMillis();
349     MdcOverride override = new MdcOverride();
350     override.addAttribute(MdcContext.MDC_START_TIME, formatter.format(startTimeInMs));
351
352     logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url);
353
354     try {
355
356       // Get a REST client instance for our request.
357       Client client = getClient();
358
359       // Debug log the request
360       debugRequest(url, payload, headers, responseType);
361
362       // Get a client request builder, and submit our GET request.
363       Builder builder = getClientBuilder(client, url, payload, headers, contentType, responseType);
364       clientResponse = operation.processOperation(builder);
365
366       populateOperationResult(clientResponse, operationResult);
367
368       // Debug log the response
369       debugResponse(operationResult, clientResponse.getHeaders());
370
371     } catch (Exception ex) {
372
373       logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage());
374       operationResult.setResultCode(500);
375       operationResult.setFailureCause(
376           "Error during GET operation to AAI with message = " + ex.getLocalizedMessage());
377
378     } finally {
379
380       if (logger.isDebugEnabled()) {
381         logger.debug(baos.toString());
382       }
383
384       // Not every valid response code is actually represented by the Response.Status
385       // object, so we need to guard against missing codes, otherwise we throw null
386       // pointer exceptions when we try to generate our metrics logs...
387       Response.Status responseStatus =
388           Response.Status.fromStatusCode(operationResult.getResultCode());
389       String responseStatusCodeString = "";
390       if (responseStatus != null) {
391         responseStatusCodeString = responseStatus.toString();
392       }
393
394       metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME,
395           new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString)
396               .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode())
397               .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()),
398           override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url);
399       logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType,
400           Long.toString(System.currentTimeMillis() - startTimeInMs), url);
401       logger.info(RestClientMsgs.HTTP_RESPONSE, url,
402           operationResult.getResultCode() + " " + responseStatusCodeString);
403     }
404
405     return operationResult;
406   }
407
408   /**
409    * This method submits an HTTP PUT request against the supplied URL.
410    *
411    * @param url - The REST endpoint to submit the PUT request to.
412    * @param payload - the payload to send to the supplied URL
413    * @param headers - The headers that should be passed in the request
414    * @param contentType - The content type of the payload
415    * @param responseType - The expected format of the response.
416    *
417    * @return The result of the PUT request.
418    */
419   public OperationResult put(String url, String payload, Map<String, List<String>> headers,
420       MediaType contentType, MediaType responseType) {
421     return processRequest(putOp, url, payload, headers, contentType, responseType);
422   }
423
424   /**
425    * This method submits an HTTP POST request against the supplied URL.
426    *
427    * @param url - The REST endpoint to submit the POST request to.
428    * @param payload - the payload to send to the supplied URL
429    * @param headers - The headers that should be passed in the request
430    * @param contentType - The content type of the payload
431    * @param responseType - The expected format of the response.
432    *
433    * @return The result of the POST request.
434    */
435   public OperationResult post(String url, String payload, Map<String, List<String>> headers,
436       MediaType contentType, MediaType responseType) {
437     return processRequest(postOp, url, payload, headers, contentType, responseType);
438   }
439   
440   /**
441    * This method submits an HTTP POST request against the supplied URL, and emulates a PATCH
442    * operation by setting a special header value
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 patch(String url, String payload, Map<String, List<String>> headers,
453       MediaType contentType, MediaType responseType) {
454     return processRequest(patchOp, url, payload, headers, contentType, responseType);
455   }
456
457   
458   /**
459    * This method submits an HTTP HEAD request against the supplied URL
460    *
461    * @param url - The REST endpoint to submit the POST request to.
462    * @param headers - The headers that should be passed in the request
463    * @param responseType - The expected format of the response.
464    *
465    * @return The result of the POST request.
466    */
467   public OperationResult head(String url, Map<String, List<String>> headers,
468       MediaType responseType) {
469     return processRequest(headOp, url, null, headers, null, responseType);
470   }
471   
472   /**
473    * This method submits an HTTP GET request against the supplied URL.
474    *
475    * @param url - The REST endpoint to submit the GET request to.
476    * @param headers - The headers that should be passed in the request
477    * @param responseType - The expected format of the response.
478    *
479    * @return The result of the GET request.
480    */
481   public OperationResult get(String url, Map<String, List<String>> headers,
482       MediaType responseType) {
483     return processRequest(getOp, url, null, headers, null, responseType);
484   }
485
486   /**
487    * This method submits an HTTP GET request against the supplied URL. 
488    * This variant of the method will perform a requested number of retries in the event that the
489    * first request is unsuccessful.
490    * 
491    * @param url - The REST endpoint to submit the GET request to.
492    * @param headers - The headers that should be passed in the request
493    * @param responseType - The expected format of the response.
494    * @param numRetries - The number of times to try resubmitting the request in the event of a
495    *        failure.
496    * 
497    * @return The result of the GET request.
498    */
499   public OperationResult get(String url, Map<String, List<String>> headers, MediaType responseType,
500       int numRetries) {
501     return processRequest(getOp, url, null, headers, null, responseType, numRetries);
502   }
503
504   /**
505    * This method submits an HTTP DELETE request against the supplied URL.
506    *
507    * @param url - The REST endpoint to submit the DELETE request to.
508    * @param headers - The headers that should be passed in the request
509    * @param responseType - The expected format of the response.
510    *
511    * @return The result of the DELETE request.
512    */
513   public OperationResult delete(String url, Map<String, List<String>> headers,
514       MediaType responseType) {
515     return processRequest(deleteOp, url, null, headers, null, responseType);
516   }
517
518   /**
519    * This method does a health check ("ping") against the supplied URL.
520    *
521    * @param url - The REST endpoint to attempt a health check.
522    * @param srcAppName - The name of the application using this client.
523    * @param destAppName - The name of the destination app.
524    *
525    * @return A boolean value. True if connection attempt was successful, false otherwise.
526    *
527    */
528   public boolean healthCheck(String url, String srcAppName, String destAppName) {
529     return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE);
530
531   }
532
533   /**
534    * This method does a health check ("ping") against the supplied URL.
535    *
536    * @param url - The REST endpoint to attempt a health check.
537    * @param srcAppName - The name of the application using this client.
538    * @param destAppName - The name of the destination app.
539    * @param responseType - The response type.
540    *
541    * @return A boolean value. True if connection attempt was successful, false otherwise.
542    *
543    */
544   public boolean healthCheck(String url, String srcAppName, String destAppName,
545       MediaType responseType) {
546     MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
547     headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName}));
548     headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()}));
549
550     try {
551       logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url);
552       OperationResult result = get(url, headers, responseType);
553
554       if (result != null && result.getFailureCause() == null) {
555         logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url);
556         return true;
557       } else {
558         logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url,
559             result.getFailureCause());
560         return false;
561       }
562     } catch (Exception e) {
563       logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage());
564       return false;
565     }
566   }
567
568   /**
569    * This method constructs a client request builder that can be used for submitting REST requests
570    * to the supplied URL endpoint.
571    *
572    * @param client - The REST client we will be using to talk to the server.
573    * @param url - The URL endpoint that our request will be submitted to.
574    * @param headers - The headers that should be passed in the request
575    * @param contentType - the content type of the payload
576    * @param responseType - The expected format of the response.
577    *
578    * @return A client request builder.
579    */
580   private Builder getClientBuilder(Client client, String url, String payload,
581       Map<String, List<String>> headers, MediaType contentType, MediaType responseType) {
582
583     WebResource resource = client.resource(url);
584     Builder builder = null;
585
586     builder = resource.accept(responseType);
587
588     if (contentType != null) {
589       builder.type(contentType);
590     }
591
592     if (payload != null) {
593       builder.entity(payload);
594     }
595
596     if (headers != null) {
597       for (Entry<String, List<String>> header : headers.entrySet()) {
598         builder.header(header.getKey(), header.getValue());
599       }
600       
601       if (clientBuilder.getAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) {
602         builder = builder.header(Headers.AUTHORIZATION,
603             clientBuilder.getBasicAuthenticationCredentials());
604       }
605       
606     }
607
608     return builder;
609   }
610
611   private void debugRequest(String url, String payload, Map<String, List<String>> headers,
612       MediaType responseType) {
613     if (logger.isDebugEnabled()) {
614       StringBuilder debugRequest = new StringBuilder("REQUEST:\n");
615       debugRequest.append("URL: ").append(url).append("\n");
616       debugRequest.append("Payload: ").append(payload).append("\n");
617       debugRequest.append("Response Type: ").append(responseType).append("\n");
618       if (headers != null) {
619         debugRequest.append("Headers: ");
620         for (Entry<String, List<String>> header : headers.entrySet()) {
621           debugRequest.append("\n\t").append(header.getKey()).append(":");
622           for (String headerEntry : header.getValue()) {
623             debugRequest.append("\"").append(headerEntry).append("\" ");
624           }
625         }
626       }
627       logger.debug(debugRequest.toString());
628     }
629   }
630
631   private void debugResponse(OperationResult operationResult,
632       MultivaluedMap<String, String> headers) {
633     if (logger.isDebugEnabled()) {
634       StringBuilder debugResponse = new StringBuilder("RESPONSE:\n");
635       debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n");
636       debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause())
637           .append("\n");
638       debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n");
639       if (headers != null) {
640         debugResponse.append("Headers: ");
641         for (Entry<String, List<String>> header : headers.entrySet()) {
642           debugResponse.append("\n\t").append(header.getKey()).append(":");
643           for (String headerEntry : header.getValue()) {
644             debugResponse.append("\"").append(headerEntry).append("\" ");
645           }
646         }
647       }
648       logger.debug(debugResponse.toString());
649     }
650   }
651
652   /**
653    * This method creates an instance of the low level REST client to use for communicating with the
654    * AAI, if one has not already been created, otherwise it returns the already created instance.
655    *
656    * @return A {@link Client} instance.
657    */
658   protected Client getClient() throws Exception {
659
660     /*
661      * Attempting a new way of doing non-blocking thread-safe lazy-initialization by using Java 1.8
662      * computeIfAbsent functionality. A null value will not be stored, but once a valid mapping has
663      * been established, then the same value will be returned.
664      * 
665      * One awkwardness of the computeIfAbsent is the lack of support for thrown exceptions, which
666      * required a bit of hoop jumping to preserve the original exception for the purpose of
667      * maintaining the pre-existing this API signature.
668      */
669  
670     final InitializedClient clientInstance =
671         CLIENT_CACHE.computeIfAbsent(REST_CLIENT_INSTANCE, k -> loggedClientInitialization());
672     
673     if (clientInstance.getCaughtException() != null) {
674       throw new InstantiationException(clientInstance.getCaughtException().getMessage());
675     }
676
677     return clientInstance.getClient();
678
679   }
680
681   /**
682    * This method will only be called if computerIfAbsent is true.  The return value is null, then the result is not
683    * stored in the map. 
684    * 
685    * @return a new client instance or null
686    */
687   private InitializedClient loggedClientInitialization() {
688
689     if (logger.isDebugEnabled()) {
690       logger.debug("Instantiating REST client with following parameters:");
691       logger.debug(clientBuilder.toString());
692     }
693     
694     InitializedClient initClient = new InitializedClient();
695     
696     try {
697       initClient.setClient(clientBuilder.getClient());
698     } catch ( Throwable error ) {
699       initClient.setCaughtException(error);
700     }
701     
702     return initClient;
703
704   }
705
706
707   /**
708    * This method populates the fields of an {@link OperationResult} instance based on the contents
709    * of a {@link ClientResponse} received in response to a REST request.
710    */
711   private void populateOperationResult(ClientResponse response, OperationResult opResult) {
712
713     // If we got back a NULL response, then just produce a generic
714     // error code and result indicating this.
715     if (response == null) {
716       opResult.setResultCode(500);
717       opResult.setFailureCause("Client response was null");
718       return;
719     }
720         
721     int statusCode = response.getStatus();
722     opResult.setResultCode(statusCode);
723
724     if (opResult.wasSuccessful()) {
725         if (statusCode != Response.Status.NO_CONTENT.getStatusCode()) {
726             opResult.setResult(response.getEntity(String.class));
727         }
728     } else {
729         opResult.setFailureCause(response.getEntity(String.class));
730     }
731
732     opResult.setHeaders(response.getHeaders());
733   }
734
735   private class GetRestOperation implements RestOperation {
736     public ClientResponse processOperation(Builder builder) {
737       return builder.get(ClientResponse.class);
738     }
739
740     public RequestType getRequestType() {
741       return RequestType.GET;
742     }
743   }
744
745   private class PutRestOperation implements RestOperation {
746     public ClientResponse processOperation(Builder builder) {
747       return builder.put(ClientResponse.class);
748     }
749
750     public RequestType getRequestType() {
751       return RequestType.PUT;
752     }
753   }
754
755   private class PostRestOperation implements RestOperation {
756     public ClientResponse processOperation(Builder builder) {
757       return builder.post(ClientResponse.class);
758     }
759
760     public RequestType getRequestType() {
761       return RequestType.POST;
762     }
763   }
764
765   private class DeleteRestOperation implements RestOperation {
766     public ClientResponse processOperation(Builder builder) {
767       return builder.delete(ClientResponse.class);
768     }
769
770     public RequestType getRequestType() {
771       return RequestType.DELETE;
772     }
773   }
774   
775   private class HeadRestOperation implements RestOperation {
776     public ClientResponse processOperation(Builder builder) {
777       return builder.head();
778     }
779
780     public RequestType getRequestType() {
781       return RequestType.HEAD;
782     }
783   }
784
785   private class PatchRestOperation implements RestOperation {
786
787     /**
788      * Technically there is no standarized PATCH operation for the 
789      * jersey client, but we can use the method-override approach 
790      * instead.
791      */
792     public ClientResponse processOperation(Builder builder) {
793       builder = builder.header("X-HTTP-Method-Override", "PATCH");
794       return builder.post(ClientResponse.class);
795     }
796
797     public RequestType getRequestType() {
798       return RequestType.PATCH;
799     }
800   }
801
802
803   /**
804    * Interface used wrap a Jersey REST call using a functional interface.
805    */
806   private interface RestOperation {
807
808     /**
809      * Method used to wrap the functionality of making a REST call out to the endpoint.
810      *
811      * @param builder the Jersey builder used to make the request
812      * @return the response from the REST endpoint
813      */
814     public ClientResponse processOperation(Builder builder);
815
816     /**
817      * Returns the REST request type.
818      */
819     public RequestType getRequestType();
820
821     /**
822      * The supported REST request types.
823      */
824     public enum RequestType {
825       GET, PUT, POST, DELETE, PATCH, HEAD
826     }
827   }
828   
829   /*
830    * An entity to encapsulate an expected result and a potential failure cause when returning from a
831    * functional interface during the computeIfAbsent call.
832    */
833   private class InitializedClient {
834     private Client client;
835     private Throwable caughtException;
836     
837     public InitializedClient() {
838       client = null;
839       caughtException = null;
840     }
841     
842     public Client getClient() {
843       return client;
844     }
845     public void setClient(Client client) {
846       this.client = client;
847     }
848     public Throwable getCaughtException() {
849       return caughtException;
850     }
851     public void setCaughtException(Throwable caughtException) {
852       this.caughtException = caughtException;
853     }
854   
855   }
856   
857 }