050a4d9d872482fad113222a07a9daa5a93d0848
[so/libs.git] /
1 /*
2  * ONAP-SO
3  * ============LICENSE_START==========================================
4  * ===================================================================
5  * Copyright � 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;
33 import java.util.Map.Entry;
34
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.apache.log4j.Logger;
50 import org.codehaus.jackson.JsonProcessingException;
51 import org.codehaus.jackson.map.DeserializationConfig;
52 import org.codehaus.jackson.map.ObjectMapper;
53 import org.codehaus.jackson.map.SerializationConfig;
54 import org.codehaus.jackson.map.annotate.JsonRootName;
55 import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
56
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 ObjectMapper DEFAULT_MAPPER;
66         public static ObjectMapper WRAPPED_MAPPER;
67         
68         private static Logger LOGGER = Logger.getLogger(HttpClientConnector.class);
69
70         static {
71                 DEFAULT_MAPPER = new ObjectMapper();
72
73                 DEFAULT_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
74                 DEFAULT_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT);
75                 DEFAULT_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
76                 DEFAULT_MAPPER.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
77
78                 WRAPPED_MAPPER = new ObjectMapper();
79
80                 WRAPPED_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
81                 WRAPPED_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT);
82                 WRAPPED_MAPPER.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
83                 WRAPPED_MAPPER.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
84                 WRAPPED_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
85                 WRAPPED_MAPPER.disable(DeserializationConfig.Feature.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; //HttpClients.createDefault();
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                         for(Map.Entry<String, List<Object> > entry : request.queryParams().entrySet()) {
105                                 for (Object o : entry.getValue()) {
106                                         uriBuilder.setParameter(entry.getKey(), String.valueOf(o));
107                                 }
108                         }
109                         
110                         uri = uriBuilder.build();
111                 } catch (URISyntaxException e) {
112                         throw new HttpClientException (e);
113                 }
114
115                 HttpEntity entity = null;
116                 if (request.entity() != null) {
117                         // Flatten the entity to a Json string
118                                         
119                         try {
120                         // Get appropriate mapper, based on existence of a root element in Entity class
121                                 ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass());
122
123                                 String entityJson = mapper.writeValueAsString (request.entity().getEntity());
124                                 entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType()));
125
126                                 LOGGER.debug("Openstack query JSON:"+entityJson);
127                                 LOGGER.debug ("Request JSON Body: " + entityJson.replaceAll("\"password\":\"[^\"]*\"", "\"password\":\"***\""));
128
129                         } catch (JsonProcessingException e) {
130                                 throw new HttpClientException ("Json processing error on request entity", e);
131                         } catch (IOException e) {
132                                 throw new HttpClientException ("Json IO error on request entity", e);
133                         }
134                 }
135                 
136                 // Determine the HttpRequest class based on the method
137                 HttpUriRequest httpRequest;
138                 
139                 switch (request.method()) {
140                 case POST:
141                         HttpPost post = new HttpPost(uri);
142                         post.setEntity (entity);
143                         httpRequest = post;
144                         break;
145                         
146                 case GET:
147                         httpRequest = new HttpGet(uri);
148                         break;
149
150                 case PUT:
151                         HttpPut put = new HttpPut(uri);
152                         put.setEntity (entity);
153                         httpRequest = put;
154                         break;
155                         
156                 case DELETE:
157                         httpRequest = new HttpDelete(uri);
158                         break;
159                         
160                 default:
161                         throw new HttpClientException ("Unrecognized HTTP Method: " + request.method());
162                 }
163                 
164                 for (Entry<String, List<Object>> h : request.headers().entrySet()) {
165                         StringBuilder sb = new StringBuilder();
166                         for (Object v : h.getValue()) {
167                                 sb.append(String.valueOf(v));
168                         }
169                         httpRequest.addHeader(h.getKey(), sb.toString());
170                 }
171
172                 LOGGER.debug ("Sending HTTP request: " + httpRequest.toString());
173                 
174                 // Get the Response.  But don't get the body entity yet, as this response
175                 // will be wrapped in an HttpClientResponse.  The HttpClientResponse
176                 // buffers the body in constructor, so can close the response here.
177                 HttpClientResponse httpClientResponse = null;
178                 CloseableHttpResponse httpResponse = null;
179                 
180                 // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions
181                 // so calling functions can distinguish.  Only RuntimeExceptions are allowed.
182                 try {
183                         httpResponse = httpClient.execute(httpRequest);
184
185                         LOGGER.debug ("Response status: " + httpResponse.getStatusLine().getStatusCode());
186                         
187                         httpClientResponse = new HttpClientResponse (httpResponse);
188
189                         int status = httpResponse.getStatusLine().getStatusCode();
190                         if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED ||
191                                 status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_ACCEPTED)
192                         {
193                                 return httpClientResponse;
194                         }
195                 }
196                 catch (HttpResponseException e) {
197                         // What exactly does this mean?  It does not appear to get thrown for
198                         // non-2XX responses as documented.
199                         throw new OpenStackResponseException(e.getMessage(), e.getStatusCode());
200                 }
201                 catch (UnknownHostException e) {
202                         throw new OpenStackConnectException("Unknown Host: " + e.getMessage());
203                 }
204                 catch (IOException e) {
205                         // Catch all other IOExceptions and throw as OpenStackConnectException
206                         throw new OpenStackConnectException(e.getMessage());
207                 }
208                 catch (Exception e) {
209                         // Catchall for anything else, must throw as a RuntimeException
210                         LOGGER.error ("Unexpected client exception: " +e.getMessage());
211                         throw new RuntimeException("Unexpected client exception", e);
212                 }
213                 finally {
214                         // Have the body.  Close the stream
215                         if (httpResponse != null)
216                                 try {
217                                         httpResponse.close();
218                                 } catch (IOException e) {
219                                         LOGGER.warn("Unable to close HTTP Response: " + e);
220                                 }
221                 }
222                 
223                 // Get here on an error response (4XX-5XX)
224                 throw new OpenStackResponseException(httpResponse.getStatusLine().getReasonPhrase(),
225                                                                                         httpResponse.getStatusLine().getStatusCode(),
226                                                                                         httpClientResponse);
227         }
228
229 }