2 * ============LICENSE_START=======================================================
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 * ============LICENSE_END=========================================================
18 * ============LICENSE_START==========================================
19 * ===================================================================
20 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
21 * ===================================================================
22 * Licensed under the Apache License, Version 2.0 (the "License");
23 * you may not use this file except in compliance with the License.
24 * You may obtain a copy of the License at
26 * http://www.apache.org/licenses/LICENSE-2.0
28 * Unless required by applicable law or agreed to in writing, software
29 * distributed under the License is distributed on an "AS IS" BASIS,
30 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 * See the License for the specific language governing permissions and
32 * limitations under the License.
33 * ============LICENSE_END============================================
35 * ECOMP and OpenECOMP are trademarks
36 * and service marks of AT&T Intellectual Property.
40 package com.woorea.openstack.connector;
42 import java.io.IOException;
44 import java.net.URISyntaxException;
45 import java.net.UnknownHostException;
46 import java.util.List;
48 import java.util.Map.Entry;
50 import org.apache.http.HttpEntity;
51 import org.apache.http.HttpStatus;
52 import org.apache.http.client.HttpResponseException;
53 import org.apache.http.client.methods.CloseableHttpResponse;
54 import org.apache.http.client.methods.HttpDelete;
55 import org.apache.http.client.methods.HttpGet;
56 import org.apache.http.client.methods.HttpPost;
57 import org.apache.http.client.methods.HttpPut;
58 import org.apache.http.client.methods.HttpUriRequest;
59 import org.apache.http.client.utils.URIBuilder;
60 import org.apache.http.entity.ContentType;
61 import org.apache.http.entity.StringEntity;
62 import org.apache.http.impl.client.CloseableHttpClient;
63 import org.apache.http.impl.client.HttpClients;
64 import org.apache.log4j.Logger;
65 import org.codehaus.jackson.JsonProcessingException;
66 import org.codehaus.jackson.map.DeserializationConfig;
67 import org.codehaus.jackson.map.ObjectMapper;
68 import org.codehaus.jackson.map.SerializationConfig;
69 import org.codehaus.jackson.map.annotate.JsonRootName;
70 import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
72 import com.woorea.openstack.base.client.OpenStackClientConnector;
73 import com.woorea.openstack.base.client.OpenStackConnectException;
74 import com.woorea.openstack.base.client.OpenStackRequest;
75 import com.woorea.openstack.base.client.OpenStackResponse;
76 import com.woorea.openstack.base.client.OpenStackResponseException;
78 public class HttpClientConnector implements OpenStackClientConnector {
80 public static ObjectMapper DEFAULT_MAPPER;
81 public static ObjectMapper WRAPPED_MAPPER;
83 private static Logger LOGGER = Logger.getLogger(HttpClientConnector.class);
86 DEFAULT_MAPPER = new ObjectMapper();
88 DEFAULT_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
89 DEFAULT_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT);
90 DEFAULT_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
91 DEFAULT_MAPPER.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
93 WRAPPED_MAPPER = new ObjectMapper();
95 WRAPPED_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
96 WRAPPED_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT);
97 WRAPPED_MAPPER.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
98 WRAPPED_MAPPER.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
99 WRAPPED_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
100 WRAPPED_MAPPER.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
103 protected static <T> ObjectMapper getObjectMapper (Class<T> type) {
104 return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER;
107 public <T> OpenStackResponse request(OpenStackRequest<T> request) {
109 CloseableHttpClient httpClient = null; //HttpClients.createDefault();
110 httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).build();
114 // Build the URI with query params
116 URIBuilder uriBuilder = new URIBuilder(request.endpoint() + request.path());
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));
124 uri = uriBuilder.build();
125 } catch (URISyntaxException e) {
126 throw new HttpClientException (e);
129 HttpEntity entity = null;
130 if (request.entity() != null) {
131 // Flatten the entity to a Json string
134 // Get appropriate mapper, based on existence of a root element in Entity class
135 ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass());
137 String entityJson = mapper.writeValueAsString (request.entity().getEntity());
138 entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
140 System.out.println("Openstack query JSON:"+entityJson);
141 LOGGER.debug ("Request JSON Body: " + entityJson.replaceAll("\"password\":\"[^\"]*\"", "\"password\":\"***\""));
143 } catch (JsonProcessingException e) {
144 throw new HttpClientException ("Json processing error on request entity", e);
145 } catch (IOException e) {
146 throw new HttpClientException ("Json IO error on request entity", e);
150 // Determine the HttpRequest class based on the method
151 HttpUriRequest httpRequest;
153 switch (request.method()) {
155 HttpPost post = new HttpPost(uri);
156 post.setEntity (entity);
161 httpRequest = new HttpGet(uri);
165 HttpPut put = new HttpPut(uri);
166 put.setEntity (entity);
171 httpRequest = new HttpDelete(uri);
175 throw new HttpClientException ("Unrecognized HTTP Method: " + request.method());
178 for (Entry<String, List<Object>> h : request.headers().entrySet()) {
179 StringBuilder sb = new StringBuilder();
180 for (Object v : h.getValue()) {
181 sb.append(String.valueOf(v));
183 httpRequest.addHeader(h.getKey(), sb.toString());
186 LOGGER.debug ("Sending HTTP request: " + httpRequest.toString());
188 // Get the Response. But don't get the body entity yet, as this response
189 // will be wrapped in an HttpClientResponse. The HttpClientResponse
190 // buffers the body in constructor, so can close the response here.
191 HttpClientResponse httpClientResponse = null;
192 CloseableHttpResponse httpResponse = null;
194 // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions
195 // so calling functions can distinguish. Only RuntimeExceptions are allowed.
197 httpResponse = httpClient.execute(httpRequest);
199 LOGGER.debug ("Response status: " + httpResponse.getStatusLine().getStatusCode());
201 httpClientResponse = new HttpClientResponse (httpResponse);
203 int status = httpResponse.getStatusLine().getStatusCode();
204 if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED ||
205 status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_ACCEPTED)
207 return httpClientResponse;
210 catch (HttpResponseException e) {
211 // What exactly does this mean? It does not appear to get thrown for
212 // non-2XX responses as documented.
213 throw new OpenStackResponseException(e.getMessage(), e.getStatusCode());
215 catch (UnknownHostException e) {
216 throw new OpenStackConnectException("Unknown Host: " + e.getMessage());
218 catch (IOException e) {
219 // Catch all other IOExceptions and throw as OpenStackConnectException
220 throw new OpenStackConnectException(e.getMessage());
222 catch (Exception e) {
223 // Catchall for anything else, must throw as a RuntimeException
225 throw new RuntimeException("Unexpected client exception", e);
228 // Have the body. Close the stream
229 if (httpResponse != null)
231 httpResponse.close();
232 } catch (IOException e) {
233 LOGGER.warn("Unable to close HTTP Response: " + e);
237 // Get here on an error response (4XX-5XX)
238 throw new OpenStackResponseException(httpResponse.getStatusLine().getReasonPhrase(),
239 httpResponse.getStatusLine().getStatusCode(),