3 * ============LICENSE_START==========================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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============================================
20 * ECOMP and OpenECOMP are trademarks
21 * and service marks of AT&T Intellectual Property.
25 package com.woorea.openstack.connector;
27 import java.io.IOException;
29 import java.net.URISyntaxException;
30 import java.net.UnknownHostException;
31 import java.util.List;
33 import java.util.Map.Entry;
35 import org.apache.http.HttpEntity;
36 import org.apache.http.HttpStatus;
37 import org.apache.http.client.HttpResponseException;
38 import org.apache.http.client.methods.CloseableHttpResponse;
39 import org.apache.http.client.methods.HttpDelete;
40 import org.apache.http.client.methods.HttpGet;
41 import org.apache.http.client.methods.HttpPost;
42 import org.apache.http.client.methods.HttpPut;
43 import org.apache.http.client.methods.HttpUriRequest;
44 import org.apache.http.client.utils.URIBuilder;
45 import org.apache.http.entity.ContentType;
46 import org.apache.http.entity.StringEntity;
47 import org.apache.http.impl.client.CloseableHttpClient;
48 import org.apache.http.impl.client.HttpClients;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.fasterxml.jackson.annotation.JsonInclude.Include;
53 import com.fasterxml.jackson.annotation.JsonRootName;
54 import com.fasterxml.jackson.core.JsonProcessingException;
55 import com.fasterxml.jackson.databind.DeserializationFeature;
56 import com.fasterxml.jackson.databind.ObjectMapper;
57 import com.fasterxml.jackson.databind.SerializationFeature;
58 import com.woorea.openstack.base.client.OpenStackClientConnector;
59 import com.woorea.openstack.base.client.OpenStackConnectException;
60 import com.woorea.openstack.base.client.OpenStackRequest;
61 import com.woorea.openstack.base.client.OpenStackResponse;
62 import com.woorea.openstack.base.client.OpenStackResponseException;
64 public class HttpClientConnector implements OpenStackClientConnector {
66 public static final ObjectMapper DEFAULT_MAPPER;
67 public static final ObjectMapper WRAPPED_MAPPER;
69 private static Logger LOGGER = LoggerFactory.getLogger(HttpClientConnector.class);
72 DEFAULT_MAPPER = new ObjectMapper();
74 DEFAULT_MAPPER.setSerializationInclusion(Include.NON_NULL);
75 DEFAULT_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
76 DEFAULT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
77 DEFAULT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
79 WRAPPED_MAPPER = new ObjectMapper();
81 WRAPPED_MAPPER.setSerializationInclusion(Include.NON_NULL);
82 WRAPPED_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
83 WRAPPED_MAPPER.enable(SerializationFeature.WRAP_ROOT_VALUE);
84 WRAPPED_MAPPER.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
85 WRAPPED_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
86 WRAPPED_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
89 protected static <T> ObjectMapper getObjectMapper (Class<T> type) {
90 return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER;
94 public <T> OpenStackResponse request(OpenStackRequest<T> request) {
96 CloseableHttpClient httpClient = null;
97 httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).build();
101 // Build the URI with query params
103 URIBuilder uriBuilder = new URIBuilder(request.endpoint() + request.path());
105 setUriBuilderParams(request, uriBuilder);
107 uri = uriBuilder.build();
108 } catch (URISyntaxException e) {
109 throw new HttpClientException (e);
112 HttpEntity entity = null;
113 if (request.entity() != null) {
114 // Flatten the entity to a Json string
117 // Get appropriate mapper, based on existence of a root element in Entity class
118 ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass());
120 String entityJson = mapper.writeValueAsString (request.entity().getEntity());
121 entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
123 LOGGER.debug ("Request JSON Body: " + entityJson.replaceAll("\"password\":\"[^\"]*\"", "\"password\":\"***\""));
125 } catch (JsonProcessingException e) {
126 throw new HttpClientException ("Json processing error on request entity", e);
127 } catch (IOException e) {
128 throw new HttpClientException ("Json IO error on request entity", e);
132 // Determine the HttpRequest class based on the method
133 HttpUriRequest httpRequest;
135 httpRequest = getHttpUriRequest(request, uri, entity);
137 for (Entry<String, List<Object>> h : request.headers().entrySet()) {
138 StringBuilder sb = new StringBuilder();
139 for (Object v : h.getValue()) {
140 sb.append(String.valueOf(v));
142 httpRequest.addHeader(h.getKey(), sb.toString());
145 LOGGER.debug ("Sending HTTP request: " + httpRequest.toString());
147 // Get the Response. But don't get the body entity yet, as this response
148 // will be wrapped in an HttpClientResponse. The HttpClientResponse
149 // buffers the body in constructor, so can close the response here.
150 HttpClientResponse httpClientResponse = null;
151 CloseableHttpResponse httpResponse = null;
153 // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions
154 // so calling functions can distinguish. Only RuntimeExceptions are allowed.
156 httpResponse = httpClient.execute(httpRequest);
158 LOGGER.debug ("Response status: " + httpResponse.getStatusLine().getStatusCode());
160 httpClientResponse = new HttpClientResponse (httpResponse);
162 int status = httpResponse.getStatusLine().getStatusCode();
163 if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED ||
164 status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_ACCEPTED)
166 return httpClientResponse;
169 catch (HttpResponseException e) {
170 // What exactly does this mean? It does not appear to get thrown for
171 // non-2XX responses as documented.
172 throw new OpenStackResponseException(e.getMessage(), e.getStatusCode());
174 catch (UnknownHostException e) {
175 throw new OpenStackConnectException("Unknown Host: " + e.getMessage());
177 catch (IOException e) {
178 // Catch all other IOExceptions and throw as OpenStackConnectException
179 throw new OpenStackConnectException(e.getMessage());
181 catch (Exception e) {
182 // Catchall for anything else, must throw as a RuntimeException
183 LOGGER.error ("Unexpected client exception: " +e.getMessage());
184 throw new RuntimeException("Unexpected client exception", e);
187 // Have the body. Close the stream
188 if (httpResponse != null)
190 httpResponse.close();
191 } catch (IOException e) {
192 LOGGER.warn("Unable to close HTTP Response: " + e);
196 // Get here on an error response (4XX-5XX)
197 throw new OpenStackResponseException(httpResponse.getStatusLine().getReasonPhrase(),
198 httpResponse.getStatusLine().getStatusCode(),
202 private <T> HttpUriRequest getHttpUriRequest(OpenStackRequest<T> request, URI uri, HttpEntity entity) {
203 HttpUriRequest httpRequest;
204 switch (request.method()) {
206 HttpPost post = new HttpPost(uri);
207 post.setEntity (entity);
212 httpRequest = new HttpGet(uri);
216 HttpPut put = new HttpPut(uri);
217 put.setEntity (entity);
222 httpRequest = new HttpDelete(uri);
226 throw new HttpClientException("Unrecognized HTTP Method: " + request.method());
231 private <T> void setUriBuilderParams(OpenStackRequest<T> request, URIBuilder uriBuilder) {
232 for(Entry<String, List<Object>> entry : request.queryParams().entrySet()) {
233 for (Object o : entry.getValue()) {
234 uriBuilder.setParameter(entry.getKey(), String.valueOf(o));