summaryrefslogtreecommitdiffstats
path: root/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http
diff options
context:
space:
mode:
Diffstat (limited to 'cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http')
-rw-r--r--cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientConnector.java245
-rw-r--r--cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientException.java42
-rw-r--r--cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientRedirectStrategy.java106
-rw-r--r--cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientResponse.java107
4 files changed, 500 insertions, 0 deletions
diff --git a/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientConnector.java b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientConnector.java
new file mode 100644
index 0000000000..421f62a437
--- /dev/null
+++ b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientConnector.java
@@ -0,0 +1,245 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 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=========================================================
+ */
+
+package org.openecomp.mso.cloudify.connector.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+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.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+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.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.openecomp.mso.cloudify.base.client.CloudifyClientConnector;
+import org.openecomp.mso.cloudify.base.client.CloudifyConnectException;
+import org.openecomp.mso.cloudify.base.client.CloudifyRequest;
+import org.openecomp.mso.cloudify.base.client.CloudifyResponse;
+import org.openecomp.mso.cloudify.base.client.CloudifyResponseException;
+import org.openecomp.mso.logger.MsoLogger;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class HttpClientConnector implements CloudifyClientConnector {
+
+ public static ObjectMapper DEFAULT_MAPPER;
+ public static ObjectMapper WRAPPED_MAPPER;
+
+ private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
+
+ static {
+ DEFAULT_MAPPER = new ObjectMapper();
+
+ DEFAULT_MAPPER.setSerializationInclusion(Include.NON_NULL);
+ DEFAULT_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
+ DEFAULT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ DEFAULT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+ WRAPPED_MAPPER = new ObjectMapper();
+
+ WRAPPED_MAPPER.setSerializationInclusion(Include.NON_NULL);
+ WRAPPED_MAPPER.disable(SerializationFeature.INDENT_OUTPUT);
+ WRAPPED_MAPPER.enable(SerializationFeature.WRAP_ROOT_VALUE);
+ WRAPPED_MAPPER.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
+ WRAPPED_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
+ WRAPPED_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ }
+
+ protected static <T> ObjectMapper getObjectMapper (Class<T> type) {
+ return type.getAnnotation(JsonRootName.class) == null ? DEFAULT_MAPPER : WRAPPED_MAPPER;
+ }
+
+ public <T> CloudifyResponse request(CloudifyRequest<T> request) {
+
+ CloseableHttpClient httpClient = null; //HttpClients.createDefault();
+
+ if (request.isBasicAuth()) {
+ // Use Basic Auth for this request.
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(AuthScope.ANY,
+ new UsernamePasswordCredentials (request.getUser(), request.getPassword()));
+
+ httpClient = HttpClients.custom().setRedirectStrategy(new HttpClientRedirectStrategy()).setDefaultCredentialsProvider(credentialsProvider).build();
+ }
+ else {
+ // Don't use basic authentication. The Client will attempt Token-based authentication
+ 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<String, List<Object> > 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) {
+ // Special handling for streaming input
+ if (request.entity().getEntity() instanceof InputStream) {
+ // Entity is an InputStream
+ entity = new InputStreamEntity ((InputStream) request.entity().getEntity());
+ }
+ else {
+ // Assume to be JSON. 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()));
+
+ 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<String, List<Object>> h : request.headers().entrySet()) {
+ StringBuilder sb = new StringBuilder();
+ for (Object v : h.getValue()) {
+ sb.append(String.valueOf(v));
+ }
+ httpRequest.addHeader(h.getKey(), sb.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 CloudifyResponseException(e.getMessage(), e.getStatusCode());
+ }
+ catch (UnknownHostException e) {
+ throw new CloudifyConnectException("Unknown Host: " + e.getMessage());
+ }
+ catch (IOException e) {
+ // Catch all other IOExceptions and throw as OpenStackConnectException
+ throw new CloudifyConnectException(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.debug("Unable to close HTTP Response: " + e);
+ }
+ }
+
+ // Get here on an error response (4XX-5XX)
+ throw new CloudifyResponseException(httpResponse.getStatusLine().getReasonPhrase(),
+ httpResponse.getStatusLine().getStatusCode(),
+ httpClientResponse);
+ }
+
+}
diff --git a/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientException.java b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientException.java
new file mode 100644
index 0000000000..d5eb675257
--- /dev/null
+++ b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientException.java
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 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=========================================================
+ */
+
+package org.openecomp.mso.cloudify.connector.http;
+
+/*
+ * 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/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientRedirectStrategy.java b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientRedirectStrategy.java
new file mode 100644
index 0000000000..809c5d0175
--- /dev/null
+++ b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientRedirectStrategy.java
@@ -0,0 +1,106 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 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=========================================================
+ */
+
+package org.openecomp.mso.cloudify.connector.http;
+
+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/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientResponse.java b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientResponse.java
new file mode 100644
index 0000000000..a49f96cf0d
--- /dev/null
+++ b/cloudify-client/src/main/java/org/openecomp/mso/cloudify/connector/http/HttpClientResponse.java
@@ -0,0 +1,107 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP - SO
+ * ================================================================================
+ * Copyright (C) 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=========================================================
+ */
+
+package org.openecomp.mso.cloudify.connector.http;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.openecomp.mso.cloudify.base.client.CloudifyResponse;
+import org.openecomp.mso.logger.MsoLogger;
+
+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 CloudifyResponse {
+
+ private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
+
+ 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> T getEntity (Class<T> 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> T getErrorEntity(Class<T> returnType) {
+ return getEntity(returnType);
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream (entityBody.getBytes());
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return response.getFirstHeader(name).getValue();
+ }
+
+ @Override
+ public Map<String, String> headers() {
+ Map<String, String> headers = new HashMap<String, String>();
+
+ Header responseHeaders[] = response.getAllHeaders();
+ for (Header h : responseHeaders) {
+ headers.put(h.getName(), h.getValue());
+ }
+
+ return headers;
+ }
+
+}