2 * ============LICENSE_START=======================================================
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
23 package org.onap.so.cloudify.connector.http;
25 import java.io.IOException;
26 import java.io.InputStream;
28 import java.net.URISyntaxException;
29 import java.net.UnknownHostException;
30 import java.util.List;
32 import java.util.Map.Entry;
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;
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;
68 public class HttpClientConnector implements CloudifyClientConnector {
70 private static ObjectMapper DEFAULT_MAPPER;
71 private static ObjectMapper WRAPPED_MAPPER;
73 private static Logger logger = LoggerFactory.getLogger(HttpClientConnector.class);
76 DEFAULT_MAPPER = new ObjectMapper();
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);
83 WRAPPED_MAPPER = new ObjectMapper();
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);
93 protected static <T> ObjectMapper getObjectMapper (Class<T> type) {
94 return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER;
97 public <T> CloudifyResponse request(CloudifyRequest<T> request) {
99 CloseableHttpClient httpClient = null; //HttpClients.createDefault();
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()));
107 httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).setDefaultCredentialsProvider(credentialsProvider).build();
110 // Don't use basic authentication. The Client will attempt Token-based authentication
111 httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).build();
116 // Build the URI with query params
118 URIBuilder uriBuilder = new URIBuilder(request.endpoint() + request.path());
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));
126 uri = uriBuilder.build();
127 } catch (URISyntaxException e) {
128 throw new HttpClientException (e);
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());
139 // Assume to be JSON. Flatten the entity to a Json string
141 // Get appropriate mapper, based on existence of a root element in Entity class
142 ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass());
144 String entityJson = mapper.writeValueAsString (request.entity().getEntity());
145 entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
147 logger.debug ("Request JSON Body: {}", entityJson.replaceAll("\"password\":\"[^\"]*\"",
148 "\"password\":\"***\""));
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);
158 // Determine the HttpRequest class based on the method
159 HttpUriRequest httpRequest;
161 switch (request.method()) {
163 HttpPost post = new HttpPost(uri);
164 post.setEntity (entity);
169 httpRequest = new HttpGet(uri);
173 HttpPut put = new HttpPut(uri);
174 put.setEntity (entity);
179 httpRequest = new HttpDelete(uri);
183 throw new HttpClientException ("Unrecognized HTTP Method: " + request.method());
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));
191 httpRequest.addHeader(h.getKey(), sb.toString());
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;
200 // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions
201 // so calling functions can distinguish. Only RuntimeExceptions are allowed.
203 httpResponse = httpClient.execute(httpRequest);
205 logger.debug ("Response status: {}", httpResponse.getStatusLine().getStatusCode());
207 httpClientResponse = new HttpClientResponse (httpResponse);
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)
213 return httpClientResponse;
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());
221 catch (UnknownHostException e) {
222 throw new CloudifyConnectException("Unknown Host: " + e.getMessage());
224 catch (IOException e) {
225 // Catch all other IOExceptions and throw as OpenStackConnectException
226 throw new CloudifyConnectException(e.getMessage());
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);
234 // Have the body. Close the stream
235 if (httpResponse != null)
237 httpResponse.close();
238 } catch (IOException e) {
239 logger.debug("Unable to close HTTP Response: " + e);
243 // Get here on an error response (4XX-5XX)
244 throw new CloudifyResponseException(httpResponse.getStatusLine().getReasonPhrase(),
245 httpResponse.getStatusLine().getStatusCode(),