cab3e5082ea7b80f17333110608492993b3ff2ac
[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
95     /**
96      * Internal method used to build an APIResponse using the specified 
97      * HttpResponse object.
98      *
99      * @param response response wrapped inside an APIResponse object
100      * @return api response
101      */
102     private APIResponse buildResponse(HttpResponse response) 
103             throws RESTException {
104
105         return new APIResponse(response);
106     }
107
108     /**
109      * Used to release any resources used by the connection.
110      * @param response HttpResponse object used for releasing the connection
111      * @throws RESTException if unable to release connection
112      *
113      */
114     private void releaseConnection(HttpResponse response) throws RESTException {
115         try {
116             EntityUtils.consume(response.getEntity());
117         } catch (IOException ioe) {
118             throw new RESTException(ioe);
119         }
120     }
121
122     /**
123      * Sets headers to the http message.
124      *
125      * @param httpMsg http message to set headers for
126      */
127     private void addInternalHeaders(AbstractHttpMessage httpMsg) {
128         if (headers.isEmpty()) {
129             return;
130         }
131
132         final Set<String> keySet = headers.keySet();
133         for (final String key : keySet) {
134             final List<String> values = headers.get(key);
135             for (final String value : values) {
136                 httpMsg.addHeader(key, value);
137             }
138         }
139     }
140
141     /**
142      * Builds the query part of a URL.
143      *
144      * @return query
145      */
146     private String buildQuery() {
147         if (this.parameters.size() == 0) {
148             return "";
149         }
150
151         StringBuilder sb = new StringBuilder();
152         String charSet = "UTF-8";
153         try {
154             Iterator<String> keyitr = this.parameters.keySet().iterator();
155             for (int i = 0; keyitr.hasNext(); ++i) {
156                 if (i > 0) {
157                     sb.append("&");
158                 }
159
160                 final String name = keyitr.next();
161                 final List<String> values = this.parameters.get(name);
162                 for(final String value : values) {
163                     sb.append(URLEncoder.encode(name, charSet));
164                     sb.append("=");
165                     sb.append(URLEncoder.encode(value, charSet));
166                 }
167             }
168         } catch (UnsupportedEncodingException e) {
169             // should not occur
170             e.printStackTrace();
171         }
172         return sb.toString();
173     }
174
175     /**
176      * Creates an http client that can be used for sending http requests.
177      *
178      * @return created http client
179      *
180      * @throws RESTException if unable to create http client.
181      */
182     private CloseableHttpClient createClient() throws RESTException {
183         //TODO - we may want to trust self signed certificate at some point - add implementation here
184         HttpClientBuilder clientBuilder;
185
186                 try {
187                         SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
188                                         (SSLSocketFactory) SSLSocketFactory.getDefault(),
189                                         new HostNameVerifier());
190                         Registry<ConnectionSocketFactory> registry = RegistryBuilder
191                                         .<ConnectionSocketFactory> create()
192                                         .register("http",
193                                                         PlainConnectionSocketFactory.getSocketFactory())
194                                         .register("https", sslSocketFactory).build();
195                         PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(
196                                         registry);
197                         clientBuilder = HttpClientBuilder.create().setConnectionManager(
198                                         manager);
199                 } catch (Exception ex) {
200                         throw new RESTException(ex.getMessage());
201                 }
202                 clientBuilder.disableRedirectHandling();
203
204                 if ((this.proxyHost != null) && (this.proxyPort != -1)) {
205                         HttpHost proxy = new HttpHost(this.proxyHost, this.proxyPort);
206                         clientBuilder.setProxy(proxy);
207                 }
208
209                 return clientBuilder.build();
210     }
211
212     /**
213      * Creates a RESTClient with the specified URL, proxy host, and proxy port.
214      *
215      * @param URL URL to send request to
216      * @param proxyHost proxy host to use for sending request
217      * @param proxyPort proxy port to use for sendin request
218      *
219      * @throws RESTException if unable to create a RESTClient
220      */
221     public RESTClient(String URL, String proxyHost, int proxyPort)
222             throws RESTException {
223         this(new RESTConfig(URL, proxyHost, proxyPort));
224     }
225
226     /**
227      * Creates a RESTClient with the specified URL. No proxy host nor port will
228      * be used. 
229      *
230      * @param URL URL to send request to
231      *
232      * @throws RESTException if unable to create a RESTClient
233      */
234     public RESTClient(String URL) throws RESTException {
235         this(new RESTConfig(URL));
236     }
237     
238     /**
239      * Creates a RESTClient with the RESTConfig object.
240      *
241      * @param RESTConfig config to use for sending request
242      *
243      * @throws RESTException if unable to create a RESTClient
244      */
245     public RESTClient(RESTConfig cfg) throws RESTException {
246         this.headers = new LinkedHashMap<String, List<String>>();
247         this.parameters = new LinkedHashMap<String, List<String>>();
248         this.URL = cfg.getURL();
249         this.proxyHost = cfg.getProxyHost();
250         this.proxyPort = cfg.getProxyPort();
251     }
252
253     /**
254      * Adds parameter to be sent during http request.
255      * <p>
256      * Does not remove any parameters with the same name, thus allowing 
257      * duplicates.
258      *
259      * @param name name of parameter
260      * @param value value of parametr
261      * @return a reference to 'this', which can be used for method chaining
262      */
263     public RESTClient addParameter(String name, String value) {
264         if (!parameters.containsKey(name)) {
265             parameters.put(name, new ArrayList<String>());
266         }
267
268         List<String> values = parameters.get(name);
269         values.add(value);
270
271         return this;
272     }
273
274     /**
275      * Sets parameter to be sent during http request.
276      * <p>
277      * Removes any parameters with the same name, thus disallowing duplicates.
278      *
279      * @param name name of parameter
280      * @param value value of parametr
281      * @return a reference to 'this', which can be used for method chaining
282      */
283     public RESTClient setParameter(String name, String value) {
284         if (parameters.containsKey(name)) {
285             parameters.get(name).clear();
286         }
287
288         addParameter(name, value);
289
290         return this;
291     }
292
293     /**
294      * Adds http header to be sent during http request.
295      * <p>
296      * Does not remove any headers with the same name, thus allowing 
297      * duplicates.
298      *
299      * @param name name of header 
300      * @param value value of header 
301      * @return a reference to 'this', which can be used for method chaining
302      */
303     public RESTClient addHeader(String name, String value) {
304         if (!headers.containsKey(name)) {
305             headers.put(name, new ArrayList<String>());
306         }
307
308         List<String> values = headers.get(name);
309         values.add(value);
310
311         return this;
312     }
313
314     /**
315      * Sets http header to be sent during http request.
316      * <p>
317      * Does not remove any headers with the same name, thus allowing 
318      * duplicates.
319      *
320      * @param name name of header 
321      * @param value value of header 
322      * @return a reference to 'this', which can be used for method chaining
323      */
324     public RESTClient setHeader(String name, String value) {
325         if (headers.containsKey(name)) {
326             headers.get(name).clear();
327         }
328
329         addHeader(name, value);
330
331         return this;
332     }
333     
334     /**
335      * Convenience method for adding the authorization header using the 
336      * specified OAuthToken object.
337      *
338      * @param token token to use for setting authorization
339      * @return a reference to 'this,' which can be used for method chaining
340      */
341     public RESTClient addAuthorizationHeader(String token) {
342         this.addHeader("Authorization", token);
343         return this;
344     }
345
346     /**
347      * Alias for httpGet().
348      *
349      * @see RESTClient#httpGet()
350      */
351     public APIResponse get() throws RESTException {
352         return httpGet();
353     }
354
355     /**
356      * Sends an http GET request using the parameters and headers previously
357      * set.
358      *
359      * @return api response
360      *
361      * @throws RESTException if request was unsuccessful
362      */
363     public APIResponse httpGet() throws RESTException {
364         HttpResponse response = null;
365
366         try (CloseableHttpClient httpClient = createClient()) {
367             String query = "";
368             if (!buildQuery().equals("")) {
369                 query = "?" + buildQuery();
370             }
371             HttpGet httpGet = new HttpGet(this.getURL() + query);
372             addInternalHeaders(httpGet);
373
374             response = httpClient.execute(httpGet);
375
376             APIResponse apiResponse = buildResponse(response);
377             return apiResponse;
378         } catch (IOException ioe) {
379             throw new RESTException(ioe);
380         } finally {
381             if (response != null) {
382                 this.releaseConnection(response);
383             }
384         }
385     }
386
387     /**
388      * Alias for httpPost()
389      *
390      * @see RESTClient#httpPost()
391      */
392     public APIResponse post() throws RESTException {
393         return httpPost();
394     }
395
396     /**
397      * Sends an http POST request.
398      * <p>
399      * POST body will be set to the values set using add/setParameter()
400      *
401      * @return api response
402      *
403      * @throws RESTException if POST was unsuccessful
404      */
405     public APIResponse httpPost() throws RESTException {
406             APIResponse response = httpPost(buildQuery()); 
407             return response;
408     }
409
410     /**
411      * Sends an http POST request using the specified body.
412      *
413      * @return api response
414      *
415      * @throws RESTException if POST was unsuccessful
416      */
417     public APIResponse httpPost(String body) throws RESTException {
418         HttpResponse response = null;
419         try (CloseableHttpClient httpClient = createClient()) {
420             HttpPost httpPost = new HttpPost(this.getURL());
421             addInternalHeaders(httpPost);
422             if (body != null && !body.equals("")) {
423                 httpEntity = new StringEntity(body);
424                 httpPost.setEntity(new StringEntity(body));
425             }
426
427             response = httpClient.execute(httpPost);
428
429             return buildResponse(response);
430         } catch (IOException e) {
431             throw new RESTException(e);
432         } finally {
433             if (response != null) {
434                 this.releaseConnection(response);
435             }
436         }
437     }
438
439     /**
440      * 
441      * @param body Data to PUT
442      * @return API response
443      * @throws RESTException 
444      */
445     public APIResponse httpPut(String body) throws RESTException {
446         HttpResponse response = null;
447         try (CloseableHttpClient httpClient = createClient()) {
448
449             String query = "";
450             if (!buildQuery().equals("")) {
451                 query = "?" + buildQuery();
452             }
453             HttpPut httpPut = new HttpPut(this.getURL() + query);
454             addInternalHeaders(httpPut);
455             if (body != null && !body.equals("")) {
456                 httpEntity = new StringEntity(body);
457                 httpPut.setEntity(httpEntity);
458             }
459
460             response = httpClient.execute(httpPut);
461
462             return buildResponse(response);
463         } catch (IOException e) {
464             throw new RESTException(e);
465         } finally {
466             if (response != null) {
467                 this.releaseConnection(response);
468             }
469         }
470     }
471
472     /**
473      * Alias for httpPatch().
474      *
475      * @see RESTClient#httpPatch()
476      */
477     public APIResponse patch(String body) throws RESTException {
478         return httpPatch(body);
479     }
480
481     /**
482      * 
483      * @param body Data to PATCH
484      * @return API response
485      * @throws RESTException 
486      */
487     public APIResponse httpPatch(String body) throws RESTException {
488         HttpResponse response = null;
489         try (CloseableHttpClient httpClient = createClient()) {
490             String query = "";
491             if (!buildQuery().equals("")) {
492                 query = "?" + buildQuery();
493             }
494             HttpPatch httpPatch = new HttpPatch(this.getURL() + query);
495             addInternalHeaders(httpPatch);
496             if (body != null && !body.equals("")) {
497                 httpEntity = new StringEntity(body);
498                 httpPatch.setEntity(httpEntity);
499             }
500
501             response = httpClient.execute(httpPatch);
502
503             return buildResponse(response);
504         } catch (IOException e) {
505             throw new RESTException(e);
506         } finally {
507             if (response != null) {
508                 this.releaseConnection(response);
509             }
510         }
511     }
512
513     /**
514      * Alias for httpDelete().
515      *
516      * @see RESTClient#httpDelete()
517      */
518     public APIResponse delete() throws RESTException {
519         return httpDelete();
520     }
521
522     /**
523      * Sends an http DELETE request using the parameters and headers previously
524      * set.
525      *
526      * @return api response
527      *
528      * @throws RESTException if request was unsuccessful
529      */
530     public APIResponse httpDelete() throws RESTException {
531         return httpDelete(null);
532     }
533
534     /**
535      * Sends an http DELETE request with a body, using the parameters and headers
536      * previously set.
537      *
538      * @return api response
539      *
540      * @throws RESTException if request was unsuccessful
541      */
542     public APIResponse httpDelete(String body) throws RESTException {
543         HttpResponse response = null;
544
545         try (CloseableHttpClient httpClient = createClient()){
546
547             String query = "";
548             if (!buildQuery().equals("")) {
549                 query = "?" + buildQuery();
550             }
551             HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(this.getURL() + query);
552             addInternalHeaders(httpDelete);
553  
554             if (body != null && !body.equals("")) {
555                 httpEntity = new StringEntity(body);
556                 httpDelete.setEntity(httpEntity);
557             }
558
559             response = httpClient.execute(httpDelete);
560
561             APIResponse apiResponse = buildResponse(response);
562             return apiResponse;
563         } catch (IOException ioe) {
564             throw new RESTException(ioe);
565         } finally {
566             if (response != null) {
567                 this.releaseConnection(response);
568             }
569         }
570     }
571
572     public String getURL() {
573         return URL;
574     }
575     public LinkedHashMap<String,List<String>> getHeaders() {
576         return headers;
577     }
578     public LinkedHashMap<String,List<String>> getParameters() {
579         return parameters;
580     }
581     public HttpEntity getHttpEntity() {
582         return httpEntity;
583     }
584
585         
586         /**
587          * Allows inclusion of a request body with DELETE.
588          */
589         private class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
590             public static final String METHOD_NAME = "DELETE";
591          
592             public String getMethod() {
593                 return METHOD_NAME;
594             }
595          
596             public HttpDeleteWithBody(final String uri) {
597                 super();
598                 setURI(URI.create(uri));
599             }
600          
601             public HttpDeleteWithBody(final URI uri) {
602                 super();
603                 setURI(uri);
604             }
605          
606             public HttpDeleteWithBody() {
607                 super();
608             }
609         }
610 }