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