From 2e984294ac28c6f2ede290c38164c5d536ccaf4a Mon Sep 17 00:00:00 2001 From: ChrisC Date: Tue, 31 Jan 2017 13:57:24 +0100 Subject: Initial OpenECOMP MSO OpenStack SDK lib commit Change-Id: Ieaacb2b2c0dcc469669880e73f0cda9fa59a6c5a Signed-off-by: ChrisC --- .../openstack/connector/HttpClientConnector.java | 227 +++++++++++++++++++++ .../openstack/connector/HttpClientException.java | 45 ++++ .../connector/HttpClientRedirectStrategy.java | 109 ++++++++++ .../openstack/connector/HttpClientResponse.java | 110 ++++++++++ 4 files changed, 491 insertions(+) create mode 100644 openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientConnector.java create mode 100644 openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientException.java create mode 100644 openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientRedirectStrategy.java create mode 100644 openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientResponse.java (limited to 'openstack-client-connectors/http-connector/src/main/java') diff --git a/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientConnector.java b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientConnector.java new file mode 100644 index 0000000..d748a79 --- /dev/null +++ b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientConnector.java @@ -0,0 +1,227 @@ +/* + * ============LICENSE_START========================================== + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + * + */ + +package com.woorea.openstack.connector; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpResponseException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.log4j.Logger; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.map.annotate.JsonRootName; +import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion; + +import com.woorea.openstack.base.client.OpenStackClientConnector; +import com.woorea.openstack.base.client.OpenStackConnectException; +import com.woorea.openstack.base.client.OpenStackRequest; +import com.woorea.openstack.base.client.OpenStackResponse; +import com.woorea.openstack.base.client.OpenStackResponseException; + +public class HttpClientConnector implements OpenStackClientConnector { + + public static ObjectMapper DEFAULT_MAPPER; + public static ObjectMapper WRAPPED_MAPPER; + + private static Logger LOGGER = Logger.getLogger(HttpClientConnector.class); + + static { + DEFAULT_MAPPER = new ObjectMapper(); + + DEFAULT_MAPPER.setSerializationInclusion(Inclusion.NON_NULL); + DEFAULT_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT); + DEFAULT_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + DEFAULT_MAPPER.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES); + + WRAPPED_MAPPER = new ObjectMapper(); + + WRAPPED_MAPPER.setSerializationInclusion(Inclusion.NON_NULL); + WRAPPED_MAPPER.disable(SerializationConfig.Feature.INDENT_OUTPUT); + WRAPPED_MAPPER.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE); + WRAPPED_MAPPER.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE); + WRAPPED_MAPPER.enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + WRAPPED_MAPPER.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES); + } + + protected static ObjectMapper getObjectMapper (Class type) { + return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER; + } + + public OpenStackResponse request(OpenStackRequest request) { + + CloseableHttpClient httpClient = null; //HttpClients.createDefault(); + httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).build(); + + URI uri = null; + + // Build the URI with query params + try { + URIBuilder uriBuilder = new URIBuilder(request.endpoint() + request.path()); + + for(Map.Entry > entry : request.queryParams().entrySet()) { + for (Object o : entry.getValue()) { + uriBuilder.setParameter(entry.getKey(), String.valueOf(o)); + } + } + + uri = uriBuilder.build(); + } catch (URISyntaxException e) { + throw new HttpClientException (e); + } + + HttpEntity entity = null; + if (request.entity() != null) { + // Flatten the entity to a Json string + + try { + // Get appropriate mapper, based on existence of a root element in Entity class + ObjectMapper mapper = getObjectMapper (request.entity().getEntity().getClass()); + + String entityJson = mapper.writeValueAsString (request.entity().getEntity()); + entity = new StringEntity(entityJson, ContentType.create(request.entity().getContentType())); + + System.out.println("Openstack query JSON:"+entityJson); + LOGGER.debug ("Request JSON Body: " + entityJson.replaceAll("\"password\":\"[^\"]*\"", "\"password\":\"***\"")); + + } catch (JsonProcessingException e) { + throw new HttpClientException ("Json processing error on request entity", e); + } catch (IOException e) { + throw new HttpClientException ("Json IO error on request entity", e); + } + } + + // Determine the HttpRequest class based on the method + HttpUriRequest httpRequest; + + switch (request.method()) { + case POST: + HttpPost post = new HttpPost(uri); + post.setEntity (entity); + httpRequest = post; + break; + + case GET: + httpRequest = new HttpGet(uri); + break; + + case PUT: + HttpPut put = new HttpPut(uri); + put.setEntity (entity); + httpRequest = put; + break; + + case DELETE: + httpRequest = new HttpDelete(uri); + break; + + default: + throw new HttpClientException ("Unrecognized HTTP Method: " + request.method()); + } + + for (Entry> h : request.headers().entrySet()) { + StringBuilder sb = new StringBuilder(); + for (Object v : h.getValue()) { + sb.append(String.valueOf(v)); + } + httpRequest.addHeader(h.getKey(), sb.toString()); + } + + LOGGER.debug ("Sending HTTP request: " + httpRequest.toString()); + + // Get the Response. But don't get the body entity yet, as this response + // will be wrapped in an HttpClientResponse. The HttpClientResponse + // buffers the body in constructor, so can close the response here. + HttpClientResponse httpClientResponse = null; + CloseableHttpResponse httpResponse = null; + + // Catch known HttpClient exceptions, and wrap them in OpenStack Client Exceptions + // so calling functions can distinguish. Only RuntimeExceptions are allowed. + try { + httpResponse = httpClient.execute(httpRequest); + + LOGGER.debug ("Response status: " + httpResponse.getStatusLine().getStatusCode()); + + httpClientResponse = new HttpClientResponse (httpResponse); + + int status = httpResponse.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || + status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_ACCEPTED) + { + return httpClientResponse; + } + } + catch (HttpResponseException e) { + // What exactly does this mean? It does not appear to get thrown for + // non-2XX responses as documented. + throw new OpenStackResponseException(e.getMessage(), e.getStatusCode()); + } + catch (UnknownHostException e) { + throw new OpenStackConnectException("Unknown Host: " + e.getMessage()); + } + catch (IOException e) { + // Catch all other IOExceptions and throw as OpenStackConnectException + throw new OpenStackConnectException(e.getMessage()); + } + catch (Exception e) { + // Catchall for anything else, must throw as a RuntimeException + e.printStackTrace(); + throw new RuntimeException("Unexpected client exception", e); + } + finally { + // Have the body. Close the stream + if (httpResponse != null) + try { + httpResponse.close(); + } catch (IOException e) { + LOGGER.warn("Unable to close HTTP Response: " + e); + } + } + + // Get here on an error response (4XX-5XX) + throw new OpenStackResponseException(httpResponse.getStatusLine().getReasonPhrase(), + httpResponse.getStatusLine().getStatusCode(), + httpClientResponse); + } + +} diff --git a/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientException.java b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientException.java new file mode 100644 index 0000000..9f93455 --- /dev/null +++ b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientException.java @@ -0,0 +1,45 @@ +/* + * ============LICENSE_START========================================== + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + * + */ + +package com.woorea.openstack.connector; + +/* + * Declare a RuntimeException since the Interface does not declare any + * throwables. Any caught exception will be wrapped in HttpClientException + */ +public class HttpClientException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public HttpClientException (String s) { + super (s); + } + + public HttpClientException (Exception e) { + super ("Caught nested exception in HttpClient", e); + } + + public HttpClientException (String s, Exception e) { + super (s, e); + } +} diff --git a/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientRedirectStrategy.java b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientRedirectStrategy.java new file mode 100644 index 0000000..88f20bb --- /dev/null +++ b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientRedirectStrategy.java @@ -0,0 +1,109 @@ +/* + * ============LICENSE_START========================================== + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + * + */ + +package com.woorea.openstack.connector; + +import java.net.URI; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.ProtocolException; +import org.apache.http.annotation.Immutable; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.protocol.HttpContext; + +/** + * Custom {@link org.apache.http.client.RedirectStrategy} implementation + * that automatically redirects all HEAD, GET and DELETE requests. + * The {@link org.apache.http.client.DefaultRedirectStrategy} only + * redirects GET and HEAD automatically, per the HTTP specification + * (POST and PUT typically have bodies and thus cannot be redirected). + * + * A custom strategy is needed for the Openstack API, which can also send + * 302 on a DELETE (by name) request, expecting the client to follow the + * redirect to perform the actual deletion. + */ +@Immutable +public class HttpClientRedirectStrategy extends DefaultRedirectStrategy { + + /** + * Redirectable methods. + */ + private static final String[] REDIRECT_METHODS = new String[] { + HttpGet.METHOD_NAME, + HttpDelete.METHOD_NAME, + HttpHead.METHOD_NAME + }; + + /** + * Determine if the request should be redirected. + * This may not actually be needed, since the REDIRECT_METHODS + * array has been updated with the DELETE. + */ + @Override + protected boolean isRedirectable(final String method) { + for (final String m: REDIRECT_METHODS) { + if (m.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + /** + * Override the default redirect handling method. As implemented + * in HttpClient, it does not preserve the method on 301 or 302 + * responses, always redirecting to a GET. + */ + @Override + public HttpUriRequest getRedirect( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + + final URI uri = getLocationURI(request, response, context); + final String method = request.getRequestLine().getMethod(); + if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { + return new HttpHead(uri); + } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) { + return new HttpGet(uri); + } else { + + final int status = response.getStatusLine().getStatusCode(); + + HttpUriRequest newRequest = null; + if (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == HttpStatus.SC_MOVED_TEMPORARILY) { + newRequest = RequestBuilder.copy(request).setUri(uri).build(); + } else { + newRequest = new HttpGet(uri); + } + return newRequest; + } + } +} diff --git a/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientResponse.java b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientResponse.java new file mode 100644 index 0000000..e1850a2 --- /dev/null +++ b/openstack-client-connectors/http-connector/src/main/java/com/woorea/openstack/connector/HttpClientResponse.java @@ -0,0 +1,110 @@ +/* + * ============LICENSE_START========================================== + * =================================================================== + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + * + */ + +package com.woorea.openstack.connector; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.log4j.Logger; +import org.codehaus.jackson.map.ObjectMapper; + +import com.woorea.openstack.base.client.OpenStackResponse; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class HttpClientResponse implements OpenStackResponse { + + private static Logger LOGGER = Logger.getLogger(HttpClientConnector.class); + + private HttpResponse response = null; + private String entityBody = null; + + public HttpClientResponse(HttpResponse response) + { + this.response = response; + + // Read the body so InputStream can be closed + if (response.getEntity() == null) { + // No body + LOGGER.debug ("No Response Body"); + return; + } + + ByteArrayOutputStream responseBody = new ByteArrayOutputStream(); + try { + response.getEntity().writeTo(responseBody); + } catch (IOException e) { + throw new HttpClientException ("Error Reading Response Body", e); + } + entityBody = responseBody.toString(); + LOGGER.debug (entityBody); + } + + + @Override + public T getEntity (Class returnType) { + // Get appropriate mapper, based on existence of a root element + ObjectMapper mapper = HttpClientConnector.getObjectMapper (returnType); + + T resp = null; + try { + resp = mapper.readValue(entityBody, returnType); + } catch (Exception e) { + throw new HttpClientException ("Caught exception in getEntity", e); + } + return resp; + } + + @Override + public T getErrorEntity(Class returnType) { + return getEntity(returnType); + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream (entityBody.getBytes()); + } + + @Override + public String header(String name) { + return response.getFirstHeader(name).getValue(); + } + + @Override + public Map headers() { + Map headers = new HashMap(); + + Header responseHeaders[] = response.getAllHeaders(); + for (Header h : responseHeaders) { + headers.put(h.getName(), h.getValue()); + } + + return headers; + } + +} -- cgit 1.2.3-korg