Replaced all tabs with spaces in java and pom.xml
[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 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 com.fasterxml.jackson.annotation.JsonInclude.Include;
58 import com.fasterxml.jackson.annotation.JsonRootName;
59 import com.fasterxml.jackson.core.JsonProcessingException;
60 import com.fasterxml.jackson.databind.DeserializationFeature;
61 import com.fasterxml.jackson.databind.ObjectMapper;
62 import com.fasterxml.jackson.databind.SerializationFeature;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 public class HttpClientConnector implements CloudifyClientConnector {
67
68     private static ObjectMapper DEFAULT_MAPPER;
69     private static ObjectMapper WRAPPED_MAPPER;
70
71     private static Logger logger = LoggerFactory.getLogger(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())
106                     .setDefaultCredentialsProvider(credentialsProvider).build();
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             } else {
136                 // Assume to be JSON. Flatten the entity to a Json string
137                 try {
138                     // Get appropriate mapper, based on existence of a root element in Entity class
139                     ObjectMapper mapper = getObjectMapper(request.entity().getEntity().getClass());
140
141                     String entityJson = mapper.writeValueAsString(request.entity().getEntity());
142                     entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
143
144                     logger.debug("Request JSON Body: {}",
145                             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 || status == HttpStatus.SC_NO_CONTENT
208                     || status == HttpStatus.SC_ACCEPTED) {
209                 return httpClientResponse;
210             }
211         } catch (HttpResponseException e) {
212             // What exactly does this mean? It does not appear to get thrown for
213             // non-2XX responses as documented.
214             throw new CloudifyResponseException(e.getMessage(), e.getStatusCode());
215         } catch (UnknownHostException e) {
216             throw new CloudifyConnectException("Unknown Host: " + e.getMessage());
217         } catch (IOException e) {
218             // Catch all other IOExceptions and throw as OpenStackConnectException
219             throw new CloudifyConnectException(e.getMessage());
220         } catch (Exception e) {
221             // Catchall for anything else, must throw as a RuntimeException
222             logger.error("Client exception", e);
223             throw new RuntimeException("Unexpected client exception", e);
224         } finally {
225             // Have the body. Close the stream
226             if (httpResponse != null)
227                 try {
228                     httpResponse.close();
229                 } catch (IOException e) {
230                     logger.debug("Unable to close HTTP Response: " + e);
231                 }
232         }
233
234         // Get here on an error response (4XX-5XX)
235         throw new CloudifyResponseException(httpResponse.getStatusLine().getReasonPhrase(),
236                 httpResponse.getStatusLine().getStatusCode(), httpClientResponse);
237     }
238
239 }