Fixed sonar issue in Cloudify comp
[so.git] / cloudify-client / src / main / java / org / onap / so / cloudify / connector / http / HttpClientConnector.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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  * Modifications Copyright (C) 2018 IBM.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.so.cloudify.connector.http;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.UnknownHostException;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32
33 import org.apache.http.HttpEntity;
34 import org.apache.http.HttpStatus;
35 import org.apache.http.auth.AuthScope;
36 import org.apache.http.auth.UsernamePasswordCredentials;
37 import org.apache.http.client.CredentialsProvider;
38 import org.apache.http.client.HttpResponseException;
39 import org.apache.http.client.methods.CloseableHttpResponse;
40 import org.apache.http.client.methods.HttpDelete;
41 import org.apache.http.client.methods.HttpGet;
42 import org.apache.http.client.methods.HttpPost;
43 import org.apache.http.client.methods.HttpPut;
44 import org.apache.http.client.methods.HttpUriRequest;
45 import org.apache.http.client.utils.URIBuilder;
46 import org.apache.http.entity.ContentType;
47 import org.apache.http.entity.InputStreamEntity;
48 import org.apache.http.entity.StringEntity;
49 import org.apache.http.impl.client.BasicCredentialsProvider;
50 import org.apache.http.impl.client.CloseableHttpClient;
51 import org.apache.http.impl.client.HttpClients;
52 import org.onap.so.cloudify.base.client.CloudifyClientConnector;
53 import org.onap.so.cloudify.base.client.CloudifyConnectException;
54 import org.onap.so.cloudify.base.client.CloudifyRequest;
55 import org.onap.so.cloudify.base.client.CloudifyResponse;
56 import org.onap.so.cloudify.base.client.CloudifyResponseException;
57 import org.onap.so.logger.MsoLogger;
58
59 import com.fasterxml.jackson.annotation.JsonInclude.Include;
60 import com.fasterxml.jackson.annotation.JsonRootName;
61 import com.fasterxml.jackson.core.JsonProcessingException;
62 import com.fasterxml.jackson.databind.DeserializationFeature;
63 import com.fasterxml.jackson.databind.ObjectMapper;
64 import com.fasterxml.jackson.databind.SerializationFeature;
65
66 public class HttpClientConnector implements CloudifyClientConnector {
67
68         private static ObjectMapper DEFAULT_MAPPER;
69         private static ObjectMapper WRAPPED_MAPPER;
70         
71     private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, HttpClientConnector.class);
72
73         static {
74                 DEFAULT_MAPPER = new ObjectMapper();
75
76                 DEFAULT_MAPPER.setSerializationInclusion(Include.NON_NULL);
77                 DEFAULT_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
78                 DEFAULT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
79                 DEFAULT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
80
81                 WRAPPED_MAPPER = new ObjectMapper();
82
83                 WRAPPED_MAPPER.setSerializationInclusion(Include.NON_NULL);
84                 WRAPPED_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
85                 WRAPPED_MAPPER.enable(SerializationFeature.WRAP_ROOT_VALUE);
86                 WRAPPED_MAPPER.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
87                 WRAPPED_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
88                 WRAPPED_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
89         }
90         
91         protected static <T> ObjectMapper getObjectMapper (Class<T> type) {
92                 return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER;
93         }
94
95         public <T> CloudifyResponse request(CloudifyRequest<T> request) {
96
97                 CloseableHttpClient httpClient = null; //HttpClients.createDefault();
98
99                 if (request.isBasicAuth()) {
100                         // Use Basic Auth for this request.
101                         CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
102                         credentialsProvider.setCredentials(AuthScope.ANY,
103                                         new UsernamePasswordCredentials (request.getUser(), request.getPassword()));
104
105                         httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).setDefaultCredentialsProvider(credentialsProvider).build();
106                 }
107                 else {
108                         // Don't use basic authentication.  The Client will attempt Token-based authentication
109                         httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).build();
110                 }
111                 
112                 URI uri = null;
113                 
114                 // Build the URI with query params
115                 try {
116                         URIBuilder uriBuilder = new URIBuilder(request.endpoint() + request.path());
117
118                         for(Map.Entry<String, List<Object> > entry : request.queryParams().entrySet()) {
119                                 for (Object o : entry.getValue()) {
120                                         uriBuilder.setParameter(entry.getKey(), String.valueOf(o));
121                                 }
122                         }
123                         
124                         uri = uriBuilder.build();
125                 } catch (URISyntaxException e) {
126                         throw new HttpClientException (e);
127                 }
128
129                 HttpEntity entity = null;
130                 if (request.entity() != null) {
131                         // Special handling for streaming input
132                         if (request.entity().getEntity() instanceof InputStream) {
133                                 // Entity is an InputStream
134                                 entity = new InputStreamEntity ((InputStream) request.entity().getEntity());
135                         }
136                         else {
137                                 // Assume to be JSON.  Flatten the entity to a Json string                                      
138                                 try {
139                                 // Get appropriate mapper, based on existence of a root element in Entity class
140                                         ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass());
141         
142                                         String entityJson = mapper.writeValueAsString (request.entity().getEntity());
143                                         entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
144                                         
145                                         LOGGER.debug ("Request JSON Body: " + entityJson.replaceAll("\"password\":\"[^\"]*\"", "\"password\":\"***\""));
146         
147                                 } catch (JsonProcessingException e) {
148                                         throw new HttpClientException ("Json processing error on request entity", e);
149                                 } catch (IOException e) {
150                                         throw new HttpClientException ("Json IO error on request entity", e);
151                                 }
152                         }
153                 }
154                 
155                 // Determine the HttpRequest class based on the method
156                 HttpUriRequest httpRequest;
157                 
158                 switch (request.method()) {
159                 case POST:
160                         HttpPost post = new HttpPost(uri);
161                         post.setEntity (entity);
162                         httpRequest = post;
163                         break;
164                         
165                 case GET:
166                         httpRequest = new HttpGet(uri);
167                         break;
168
169                 case PUT:
170                         HttpPut put = new HttpPut(uri);
171                         put.setEntity (entity);
172                         httpRequest = put;
173                         break;
174                         
175                 case DELETE:
176                         httpRequest = new HttpDelete(uri);
177                         break;
178                         
179                 default:
180                         throw new HttpClientException ("Unrecognized HTTP Method: " + request.method());
181                 }
182                 
183                 for (Entry<String, List<Object>> h : request.headers().entrySet()) {
184                         StringBuilder sb = new StringBuilder();
185                         for (Object v : h.getValue()) {
186                                 sb.append(String.valueOf(v));
187                         }
188                         httpRequest.addHeader(h.getKey(), sb.toString());
189                 }
190
191                 // Get the Response.  But don't get the body entity yet, as this response
192                 // will be wrapped in an HttpClientResponse.  The HttpClientResponse
193                 // buffers the body in constructor, so can close the response here.
194                 HttpClientResponse httpClientResponse = null;
195                 CloseableHttpResponse httpResponse = null;
196                 
197                 // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions
198                 // so calling functions can distinguish.  Only RuntimeExceptions are allowed.
199                 try {
200                         httpResponse = httpClient.execute(httpRequest);
201
202                         LOGGER.debug ("Response status: " + httpResponse.getStatusLine().getStatusCode());
203                         
204                         httpClientResponse = new HttpClientResponse (httpResponse);
205
206                         int status = httpResponse.getStatusLine().getStatusCode();
207                         if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED ||
208                                 status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_ACCEPTED)
209                         {
210                                 return httpClientResponse;
211                         }
212                 }
213                 catch (HttpResponseException e) {
214                         // What exactly does this mean?  It does not appear to get thrown for
215                         // non-2XX responses as documented.
216                         throw new CloudifyResponseException(e.getMessage(), e.getStatusCode());
217                 }
218                 catch (UnknownHostException e) {
219                         throw new CloudifyConnectException("Unknown Host: " + e.getMessage());
220                 }
221                 catch (IOException e) {
222                         // Catch all other IOExceptions and throw as OpenStackConnectException
223                         throw new CloudifyConnectException(e.getMessage());
224                 }
225                 catch (Exception e) {
226                         // Catchall for anything else, must throw as a RuntimeException
227                         LOGGER.error("Client exception", e);
228                         throw new RuntimeException("Unexpected client exception", e);
229                 }
230                 finally {
231                         // Have the body.  Close the stream
232                         if (httpResponse != null)
233                                 try {
234                                         httpResponse.close();
235                                 } catch (IOException e) {
236                                         LOGGER.debug("Unable to close HTTP Response: " + e);
237                                 }
238                 }
239                 
240                 // Get here on an error response (4XX-5XX)
241                 throw new CloudifyResponseException(httpResponse.getStatusLine().getReasonPhrase(),
242                                                                                         httpResponse.getStatusLine().getStatusCode(),
243                                                                                         httpClientResponse);
244         }
245
246 }