Initial OpenECOMP MSO commit
[so.git] / bpmn / MSORESTClient / src / main / java / org / openecomp / mso / rest / RESTClient.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * OPENECOMP - MSO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.mso.rest;
22
23 import java.io.Closeable;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.net.URI;
27 import java.net.URLEncoder;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.LinkedHashMap;
31 import java.util.List;
32 import java.util.Set;
33
34 import javax.net.ssl.SSLSocketFactory;
35
36 import org.apache.http.HttpEntity;
37 import org.apache.http.HttpHost;
38 import org.apache.http.HttpResponse;
39 import org.apache.http.client.HttpClient;
40 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
41 import org.apache.http.client.methods.HttpGet;
42 import org.apache.http.client.methods.HttpPatch;
43 import org.apache.http.client.methods.HttpPost;
44 import org.apache.http.client.methods.HttpPut;
45 import org.apache.http.config.Registry;
46 import org.apache.http.config.RegistryBuilder;
47 import org.apache.http.conn.socket.ConnectionSocketFactory;
48 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
49 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
50 import org.apache.http.entity.StringEntity;
51 import org.apache.http.impl.client.CloseableHttpClient;
52 import org.apache.http.impl.client.HttpClientBuilder;
53 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
54 import org.apache.http.message.AbstractHttpMessage;
55 import org.apache.http.util.EntityUtils;
56
57 /**
58  * Client used to send RESTFul requests.
59  * <p>
60  * Many of the methods return a reference to the 'this,' thereby allowing 
61  * method chaining. 
62  * <br>
63  * An example of usage can be found below:
64  * <pre>
65  * RESTClient client;
66  * try {
67  *     client = new RESTClient("http://www.openecomp.org");
68  *     APIResponse response = client
69  *         .setHeader("Accept", "application/json")
70  *         .setHeader("Clientid", "clientid")
71  *         .setHeader("header", "value")
72  *         .httpPost("postbody");
73  *     if (response.getStatusCode() == 200) {
74  *         System.out.println("Success!");
75  *     }
76  *  } catch (RESTException re) {
77  *      // Handle Exception
78  *  }
79  * </pre>
80  *
81  * @version 1.0
82  * @since 1.0
83  */
84 public class RESTClient {
85     private final String proxyHost;
86     private final int proxyPort;
87
88     private final String URL;
89
90     private final LinkedHashMap<String, List<String>> headers;
91     private final LinkedHashMap<String, List<String>> parameters;
92     
93     private HttpEntity httpEntity;
94     private HttpClient unitTestClient;
95
96     /**
97      * Internal method used to build an APIResponse using the specified 
98      * HttpResponse object.
99      *
100      * @param response response wrapped inside an APIResponse object
101      * @return api response
102      */
103     private APIResponse buildResponse(HttpResponse response) 
104             throws RESTException {
105
106         return new APIResponse(response);
107     }
108
109     /**
110      * Used to release any resources used by the connection.
111      * @param response HttpResponse object used for releasing the connection
112      * @throws RESTException if unable to release connection
113      *
114      */
115     private void releaseConnection(HttpResponse response) throws RESTException {
116         try {
117             EntityUtils.consume(response.getEntity());
118         } catch (IOException ioe) {
119             throw new RESTException(ioe);
120         }
121     }
122
123     /**
124      * Sets headers to the http message.
125      *
126      * @param httpMsg http message to set headers for
127      */
128     private void addInternalHeaders(AbstractHttpMessage httpMsg) {
129         if (headers.isEmpty()) {
130             return;
131         }
132
133         final Set<String> keySet = headers.keySet();
134         for (final String key : keySet) {
135             final List<String> values = headers.get(key);
136             for (final String value : values) {
137                 httpMsg.addHeader(key, value);
138             }
139         }
140     }
141
142     /**
143      * Builds the query part of a URL.
144      *
145      * @return query
146      */
147     private String buildQuery() {
148         if (this.parameters.size() == 0) {
149             return "";
150         }
151
152         StringBuilder sb = new StringBuilder();
153         String charSet = "UTF-8";
154         try {
155             Iterator<String> keyitr = this.parameters.keySet().iterator();
156             for (int i = 0; keyitr.hasNext(); ++i) {
157                 if (i > 0) {
158                     sb.append("&");
159                 }
160
161                 final String name = keyitr.next();
162                 final List<String> values = this.parameters.get(name);
163                 for(final String value : values) {
164                     sb.append(URLEncoder.encode(name, charSet));
165                     sb.append("=");
166                     sb.append(URLEncoder.encode(value, charSet));
167                 }
168             }
169         } catch (UnsupportedEncodingException e) {
170             // should not occur
171             e.printStackTrace();
172         }
173         return sb.toString();
174     }
175
176     /**
177      * Creates an http client that can be used for sending http requests.
178      *
179      * @return created http client
180      *
181      * @throws RESTException if unable to create http client.
182      */
183     private CloseableHttpClient createClient() throws RESTException {
184         //TODO - we may want to trust self signed certificate at some point - add implementation here
185         HttpClientBuilder clientBuilder;
186
187                 try {
188                         SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
189                                         (SSLSocketFactory) SSLSocketFactory.getDefault(),
190                                         new HostNameVerifier());
191                         Registry<ConnectionSocketFactory> registry = RegistryBuilder
192                                         .<ConnectionSocketFactory> create()
193                                         .register("http",
194                                                         PlainConnectionSocketFactory.getSocketFactory())
195                                         .register("https", sslSocketFactory).build();
196                         PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(
197                                         registry);
198                         clientBuilder = HttpClientBuilder.create().setConnectionManager(
199                                         manager);
200                 } catch (Exception ex) {
201                         throw new RESTException(ex.getMessage());
202                 }
203                 clientBuilder.disableRedirectHandling();
204
205                 if ((this.proxyHost != null) && (this.proxyPort != -1)) {
206                         HttpHost proxy = new HttpHost(this.proxyHost, this.proxyPort);
207                         clientBuilder.setProxy(proxy);
208                 }
209
210                 return clientBuilder.build();
211     }
212
213     /**
214      * Creates a RESTClient with the specified URL, proxy host, and proxy port.
215      *
216      * @param URL URL to send request to
217      * @param proxyHost proxy host to use for sending request
218      * @param proxyPort proxy port to use for sendin request
219      *
220      * @throws RESTException if unable to create a RESTClient
221      */
222     public RESTClient(String URL, String proxyHost, int proxyPort)
223             throws RESTException {
224         this(new RESTConfig(URL, proxyHost, proxyPort));
225     }
226
227     /**
228      * Creates a RESTClient with the specified URL. No proxy host nor port will
229      * be used. 
230      *
231      * @param URL URL to send request to
232      *
233      * @throws RESTException if unable to create a RESTClient
234      */
235     public RESTClient(String URL) throws RESTException {
236         this(new RESTConfig(URL));
237     }
238     
239     /**
240      * Creates a RESTClient with the RESTConfig object.
241      *
242      * @param RESTConfig config to use for sending request
243      *
244      * @throws RESTException if unable to create a RESTClient
245      */
246     public RESTClient(RESTConfig cfg) throws RESTException {
247         this.headers = new LinkedHashMap<String, List<String>>();
248         this.parameters = new LinkedHashMap<String, List<String>>();
249         this.URL = cfg.getURL();
250         this.proxyHost = cfg.getProxyHost();
251         this.proxyPort = cfg.getProxyPort();
252     }
253
254     /**
255      * Adds parameter to be sent during http request.
256      * <p>
257      * Does not remove any parameters with the same name, thus allowing 
258      * duplicates.
259      *
260      * @param name name of parameter
261      * @param value value of parametr
262      * @return a reference to 'this', which can be used for method chaining
263      */
264     public RESTClient addParameter(String name, String value) {
265         if (!parameters.containsKey(name)) {
266             parameters.put(name, new ArrayList<String>());
267         }
268
269         List<String> values = parameters.get(name);
270         values.add(value);
271
272         return this;
273     }
274
275     /**
276      * Sets parameter to be sent during http request.
277      * <p>
278      * Removes any parameters with the same name, thus disallowing duplicates.
279      *
280      * @param name name of parameter
281      * @param value value of parametr
282      * @return a reference to 'this', which can be used for method chaining
283      */
284     public RESTClient setParameter(String name, String value) {
285         if (parameters.containsKey(name)) {
286             parameters.get(name).clear();
287         }
288
289         addParameter(name, value);
290
291         return this;
292     }
293
294     /**
295      * Adds http header to be sent during http request.
296      * <p>
297      * Does not remove any headers with the same name, thus allowing 
298      * duplicates.
299      *
300      * @param name name of header 
301      * @param value value of header 
302      * @return a reference to 'this', which can be used for method chaining
303      */
304     public RESTClient addHeader(String name, String value) {
305         if (!headers.containsKey(name)) {
306             headers.put(name, new ArrayList<String>());
307         }
308
309         List<String> values = headers.get(name);
310         values.add(value);
311
312         return this;
313     }
314
315     /**
316      * Sets http header to be sent during http request.
317      * <p>
318      * Does not remove any headers with the same name, thus allowing 
319      * duplicates.
320      *
321      * @param name name of header 
322      * @param value value of header 
323      * @return a reference to 'this', which can be used for method chaining
324      */
325     public RESTClient setHeader(String name, String value) {
326         if (headers.containsKey(name)) {
327             headers.get(name).clear();
328         }
329
330         addHeader(name, value);
331
332         return this;
333     }
334     
335     /**
336      * Convenience method for adding the authorization header using the 
337      * specified OAuthToken object.
338      *
339      * @param token token to use for setting authorization
340      * @return a reference to 'this,' which can be used for method chaining
341      */
342     public RESTClient addAuthorizationHeader(String token) {
343         this.addHeader("Authorization", token);
344         return this;
345     }
346
347     /**
348      * Alias for httpGet().
349      *
350      * @see RESTClient#httpGet()
351      */
352     public APIResponse get() throws RESTException {
353         return httpGet();
354     }
355
356     /**
357      * Sends an http GET request using the parameters and headers previously
358      * set.
359      *
360      * @return api response
361      *
362      * @throws RESTException if request was unsuccessful
363      */
364     public APIResponse httpGet() throws RESTException {
365         HttpResponse response = null;
366
367         try (CloseableHttpClient httpClient = createClient()) {
368             String query = "";
369             if (!buildQuery().equals("")) {
370                 query = "?" + buildQuery();
371             }
372             HttpGet httpGet = new HttpGet(this.getURL() + query);
373             addInternalHeaders(httpGet);
374
375             response = httpClient.execute(httpGet);
376
377             APIResponse apiResponse = buildResponse(response);
378             return apiResponse;
379         } catch (IOException ioe) {
380             throw new RESTException(ioe);
381         } finally {
382             if (response != null) {
383                 this.releaseConnection(response);
384             }
385         }
386     }
387
388     /**
389      * Alias for httpPost()
390      *
391      * @see RESTClient#httpPost()
392      */
393     public APIResponse post() throws RESTException {
394         return httpPost();
395     }
396
397     /**
398      * Sends an http POST request.
399      * <p>
400      * POST body will be set to the values set using add/setParameter()
401      *
402      * @return api response
403      *
404      * @throws RESTException if POST was unsuccessful
405      */
406     public APIResponse httpPost() throws RESTException {
407             APIResponse response = httpPost(buildQuery()); 
408             return response;
409     }
410
411     /**
412      * Sends an http POST request using the specified body.
413      *
414      * @return api response
415      *
416      * @throws RESTException if POST was unsuccessful
417      */
418     public APIResponse httpPost(String body) throws RESTException {
419         HttpResponse response = null;
420         try (CloseableHttpClient httpClient = createClient()) {
421             HttpPost httpPost = new HttpPost(this.getURL());
422             addInternalHeaders(httpPost);
423             if (body != null && !body.equals("")) {
424                 httpEntity = new StringEntity(body);
425                 httpPost.setEntity(new StringEntity(body));
426             }
427
428             response = httpClient.execute(httpPost);
429
430             return buildResponse(response);
431         } catch (IOException e) {
432             throw new RESTException(e);
433         } finally {
434             if (response != null) {
435                 this.releaseConnection(response);
436             }
437         }
438     }
439
440     /**
441      * 
442      * @param body Data to PUT
443      * @return API response
444      * @throws RESTException 
445      */
446     public APIResponse httpPut(String body) throws RESTException {
447         HttpResponse response = null;
448         try (CloseableHttpClient httpClient = createClient()) {
449
450             String query = "";
451             if (!buildQuery().equals("")) {
452                 query = "?" + buildQuery();
453             }
454             HttpPut httpPut = new HttpPut(this.getURL() + query);
455             addInternalHeaders(httpPut);
456             if (body != null && !body.equals("")) {
457                 httpEntity = new StringEntity(body);
458                 httpPut.setEntity(httpEntity);
459             }
460
461             response = httpClient.execute(httpPut);
462
463             return buildResponse(response);
464         } catch (IOException e) {
465             throw new RESTException(e);
466         } finally {
467             if (response != null) {
468                 this.releaseConnection(response);
469             }
470         }
471     }
472
473     /**
474      * Alias for httpPatch().
475      *
476      * @see RESTClient#httpPatch()
477      */
478     public APIResponse patch(String body) throws RESTException {
479         return httpPatch(body);
480     }
481
482     /**
483      * 
484      * @param body Data to PATCH
485      * @return API response
486      * @throws RESTException 
487      */
488     public APIResponse httpPatch(String body) throws RESTException {
489         HttpResponse response = null;
490         try (CloseableHttpClient httpClient = createClient()) {
491             String query = "";
492             if (!buildQuery().equals("")) {
493                 query = "?" + buildQuery();
494             }
495             HttpPatch httpPatch = new HttpPatch(this.getURL() + query);
496             addInternalHeaders(httpPatch);
497             if (body != null && !body.equals("")) {
498                 httpEntity = new StringEntity(body);
499                 httpPatch.setEntity(httpEntity);
500             }
501
502             response = httpClient.execute(httpPatch);
503
504             return buildResponse(response);
505         } catch (IOException e) {
506             throw new RESTException(e);
507         } finally {
508             if (response != null) {
509                 this.releaseConnection(response);
510             }
511         }
512     }
513
514     /**
515      * Alias for httpDelete().
516      *
517      * @see RESTClient#httpDelete()
518      */
519     public APIResponse delete() throws RESTException {
520         return httpDelete();
521     }
522
523     /**
524      * Sends an http DELETE request using the parameters and headers previously
525      * set.
526      *
527      * @return api response
528      *
529      * @throws RESTException if request was unsuccessful
530      */
531     public APIResponse httpDelete() throws RESTException {
532         return httpDelete(null);
533     }
534
535     /**
536      * Sends an http DELETE request with a body, using the parameters and headers
537      * previously set.
538      *
539      * @return api response
540      *
541      * @throws RESTException if request was unsuccessful
542      */
543     public APIResponse httpDelete(String body) throws RESTException {
544         HttpResponse response = null;
545
546         try (CloseableHttpClient httpClient = createClient()){
547
548             String query = "";
549             if (!buildQuery().equals("")) {
550                 query = "?" + buildQuery();
551             }
552             HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(this.getURL() + query);
553             addInternalHeaders(httpDelete);
554  
555             if (body != null && !body.equals("")) {
556                 httpEntity = new StringEntity(body);
557                 httpDelete.setEntity(httpEntity);
558             }
559
560             response = httpClient.execute(httpDelete);
561
562             APIResponse apiResponse = buildResponse(response);
563             return apiResponse;
564         } catch (IOException ioe) {
565             throw new RESTException(ioe);
566         } finally {
567             if (response != null) {
568                 this.releaseConnection(response);
569             }
570         }
571     }
572
573     public String getURL() {
574         return URL;
575     }
576     public LinkedHashMap<String,List<String>> getHeaders() {
577         return headers;
578     }
579     public LinkedHashMap<String,List<String>> getParameters() {
580         return parameters;
581     }
582     public HttpEntity getHttpEntity() {
583         return httpEntity;
584     }
585
586         public HttpClient getUnitTestClient() {
587                 return unitTestClient;
588         }
589
590         public void setUnitTestClient(HttpClient unitTestClient) {
591                 this.unitTestClient = unitTestClient;
592         }    
593         
594         /**
595          * Allows inclusion of a request body with DELETE.
596          */
597         private class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
598             public static final String METHOD_NAME = "DELETE";
599          
600             public String getMethod() {
601                 return METHOD_NAME;
602             }
603          
604             public HttpDeleteWithBody(final String uri) {
605                 super();
606                 setURI(URI.create(uri));
607             }
608          
609             public HttpDeleteWithBody(final URI uri) {
610                 super();
611                 setURI(uri);
612             }
613          
614             public HttpDeleteWithBody() {
615                 super();
616             }
617         }
618 }