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