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