diff --git a/pom.xml b/pom.xml
index 95b90db423e..b5e380ed2fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
18.0
18.0
6.2.0-3.1
- 4.3.6
+ 4.5
4.4
3.1
5.1.34
diff --git a/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java b/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java
new file mode 100644
index 00000000000..5c291557519
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java
@@ -0,0 +1,118 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.log4j.Logger;
+
+public class BasicRestClient implements RestClient {
+
+ private static final Logger s_logger = Logger.getLogger(BasicRestClient.class);
+
+ private static final String HTTPS = HttpConstants.HTTPS;
+ private static final int HTTPS_PORT = HttpConstants.HTTPS_PORT;
+
+ private final CloseableHttpClient client;
+ private final HttpClientContext clientContext;
+
+ private BasicRestClient(final Builder> builder) {
+ client = builder.client;
+ clientContext = builder.clientContext;
+ clientContext.setTargetHost(buildHttpHost(builder.host));
+ }
+
+ protected BasicRestClient(final CloseableHttpClient client, final HttpClientContext clientContex, final String host) {
+ this.client = client;
+ clientContext = clientContex;
+ clientContext.setTargetHost(buildHttpHost(host));
+ }
+
+ private static HttpHost buildHttpHost(final String host) {
+ return new HttpHost(host, HTTPS_PORT, HTTPS);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static Builder create() {
+ return new Builder();
+ }
+
+ @Override
+ public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException {
+ logRequestExecution(request);
+ try {
+ return client.execute(clientContext.getTargetHost(), request, clientContext);
+ } catch (final IOException e) {
+ throw new CloudstackRESTException("Could not execute request " + request, e);
+ }
+ }
+
+ private void logRequestExecution(final HttpUriRequest request) {
+ final URI uri = request.getURI();
+ String query = uri.getQuery();
+ query = query != null ? "?" + query : "";
+ s_logger.debug("Executig " + request.getMethod() + " request on " + clientContext.getTargetHost() + uri.getPath() + query);
+ }
+
+ @Override
+ public void closeResponse(final CloseableHttpResponse response) throws CloudstackRESTException {
+ try {
+ s_logger.debug("Closing HTTP connection");
+ response.close();
+ } catch (final IOException e) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Failed to close response object for request.\nResponse: ").append(response);
+ throw new CloudstackRESTException(sb.toString(), e);
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ protected static class Builder {
+ private CloseableHttpClient client;
+ private HttpClientContext clientContext = HttpClientContext.create();
+ private String host;
+
+ public T client(final CloseableHttpClient client) {
+ this.client = client;
+ return (T) this;
+ }
+
+ public T clientContext(final HttpClientContext clientContext) {
+ this.clientContext = clientContext;
+ return (T) this;
+ }
+
+ public T host(final String host) {
+ this.host = host;
+ return (T) this;
+ }
+
+ public BasicRestClient build() {
+ return new BasicRestClient(this);
+ }
+ }
+
+}
diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java b/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java
new file mode 100644
index 00000000000..1c6f564f818
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java
@@ -0,0 +1,71 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.LaxRedirectStrategy;
+import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
+
+public class HttpClientHelper {
+
+ private static final String HTTPS = HttpConstants.HTTPS;
+
+ public static CloseableHttpClient createHttpClient(final int maxRedirects) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
+ final Registry socketFactoryRegistry = createSocketFactoryConfigration();
+ final BasicCookieStore cookieStore = new BasicCookieStore();
+ return HttpClientBuilder.create()
+ .setConnectionManager(new PoolingHttpClientConnectionManager(socketFactoryRegistry))
+ .setRedirectStrategy(new LaxRedirectStrategy())
+ .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).setMaxRedirects(maxRedirects).build())
+ .setDefaultCookieStore(cookieStore)
+ .setRetryHandler(new StandardHttpRequestRetryHandler())
+ .build();
+ }
+
+ private static Registry createSocketFactoryConfigration() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
+ Registry socketFactoryRegistry;
+ final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
+ final SSLConnectionSocketFactory cnnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+ socketFactoryRegistry = RegistryBuilder. create()
+ .register(HTTPS, cnnectionSocketFactory)
+ .build();
+
+ return socketFactoryRegistry;
+ }
+
+}
diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java b/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java
new file mode 100644
index 00000000000..93502a55557
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java
@@ -0,0 +1,34 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+public class HttpConstants {
+
+ public static final int HTTPS_PORT = 443;
+ public static final String HTTPS = "https";
+ public static final String GET_METHOD_TYPE = "get";
+ public static final String DELETE_METHOD_TYPE = "delete";
+ public static final String PUT_METHOD_TYPE = "put";
+ public static final String POST_METHOD_TYPE = "post";
+ public static final String TEXT_HTML_CONTENT_TYPE = "text/html";
+ public static final String JSON_CONTENT_TYPE = "application/json";
+ public static final String CONTENT_TYPE = "Content-Type";
+
+}
diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java b/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java
new file mode 100644
index 00000000000..7fecad02037
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java
@@ -0,0 +1,41 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+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;
+
+public enum HttpMethods {
+
+ GET(HttpGet.METHOD_NAME), POST(HttpPost.METHOD_NAME), PUT(HttpPut.METHOD_NAME), DELETE(HttpDelete.METHOD_NAME);
+
+ private final String name;
+
+ private HttpMethods(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
new file mode 100644
index 00000000000..4fb3a1d5229
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
@@ -0,0 +1,119 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.http.Consts;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicHeader;
+import org.springframework.util.Assert;
+
+import com.google.common.base.Optional;
+
+public class HttpUriRequestBuilder {
+
+ private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE;
+ private static final String JSON_CONTENT_TYPE = HttpConstants.JSON_CONTENT_TYPE;
+
+ private static final Optional ABSENT = Optional.absent();
+
+ private HttpMethods method;
+ private String path;
+ private Optional jsonPayload = ABSENT;
+ private final Map parameters = new HashMap();
+ private final Map methodParameters = new HashMap();
+
+ private HttpUriRequestBuilder() {
+
+ }
+
+ public static HttpUriRequestBuilder create() {
+ return new HttpUriRequestBuilder();
+ }
+
+ public HttpUriRequestBuilder method(final HttpMethods method) {
+ this.method = method;
+ return this;
+ }
+
+ public HttpUriRequestBuilder path(final String path) {
+ this.path = path;
+ return this;
+ }
+
+ public HttpUriRequestBuilder jsonPayload(final Optional jsonPayload) {
+ this.jsonPayload = jsonPayload;
+ return this;
+ }
+
+ public HttpUriRequestBuilder parameters(final Map parameters) {
+ this.parameters.clear();
+ this.parameters.putAll(parameters);
+ return this;
+ }
+
+ public HttpUriRequestBuilder methodParameters(final Map methodParameters) {
+ this.methodParameters.clear();
+ this.methodParameters.putAll(methodParameters);
+ return this;
+ }
+
+ public HttpUriRequest build() {
+ validate();
+ final RequestBuilder builder = RequestBuilder.create(method.toString()).setUri(buildUri());
+ if (!methodParameters.isEmpty()) {
+ for (final Entry entry : methodParameters.entrySet()) {
+ builder.addParameter(entry.getKey(), entry.getValue());
+ }
+ }
+ if (jsonPayload.isPresent()) {
+ builder.addHeader(new BasicHeader(CONTENT_TYPE, JSON_CONTENT_TYPE))
+ .setEntity(new StringEntity(jsonPayload.get(), ContentType.create(JSON_CONTENT_TYPE, Consts.UTF_8)));
+ }
+ return builder.build();
+ }
+
+ private void validate() {
+ Assert.notNull(method, "HTTP Method cannot be null");
+ Assert.hasText(path, "target path must be defined");
+ Assert.isTrue(path.startsWith("/"), "targte path must start with a '/' character");
+ }
+
+ private URI buildUri() {
+ try {
+ final URIBuilder builder = new URIBuilder().setPath(path);
+ for (final Map.Entry entry : parameters.entrySet()) {
+ builder.addParameter(entry.getKey(), entry.getValue());
+ }
+ return builder.build();
+ } catch (final URISyntaxException e) {
+ throw new IllegalArgumentException("Unable to build REST Service URI", e);
+ }
+ }
+}
diff --git a/utils/src/main/java/com/cloud/utils/rest/RestClient.java b/utils/src/main/java/com/cloud/utils/rest/RestClient.java
new file mode 100644
index 00000000000..104f09bd8f5
--- /dev/null
+++ b/utils/src/main/java/com/cloud/utils/rest/RestClient.java
@@ -0,0 +1,31 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+
+public interface RestClient {
+
+ public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException;
+
+ public void closeResponse(final CloseableHttpResponse response) throws CloudstackRESTException;
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java b/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java
new file mode 100644
index 00000000000..c30103ef20a
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java
@@ -0,0 +1,106 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import org.apache.http.HttpHost;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicStatusLine;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class BasicRestClientTest {
+
+ private static final String LOCALHOST = "localhost";
+ private static final String HTTPS = HttpConstants.HTTPS;
+
+ private static final StatusLine HTTP_200_REPSONSE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 200, "OK");
+ private static final StatusLine HTTP_503_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 503, "Service unavailable");
+
+ private static final CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);
+
+ private static CloseableHttpClient httpClient;
+ private static HttpUriRequest request;
+
+ @BeforeClass
+ public static void setupClass() throws Exception {
+ request = HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("/path")
+ .build();
+ httpClient = spy(HttpClientHelper.createHttpClient(2));
+ }
+
+ @Test
+ public void testExecuteRequest() throws Exception {
+ when(mockResponse.getStatusLine()).thenReturn(HTTP_200_REPSONSE);
+ doReturn(mockResponse).when(httpClient).execute(any(HttpHost.class), HttpRequestMatcher.eq(request), any(HttpClientContext.class));
+ final BasicRestClient restClient = BasicRestClient.create()
+ .host(LOCALHOST)
+ .client(httpClient)
+ .build();
+
+ final CloseableHttpResponse response = restClient.execute(request);
+
+ assertThat(response, notNullValue());
+ assertThat(response, sameInstance(mockResponse));
+ assertThat(response.getStatusLine(), sameInstance(HTTP_200_REPSONSE));
+ }
+
+ @Test
+ public void testExecuteRequestStatusCodeIsNotOk() throws Exception {
+ when(mockResponse.getStatusLine()).thenReturn(HTTP_503_STATUSLINE);
+ doReturn(mockResponse).when(httpClient).execute(any(HttpHost.class), HttpRequestMatcher.eq(request), any(HttpClientContext.class));
+ final BasicRestClient restClient = BasicRestClient.create()
+ .host(LOCALHOST)
+ .client(httpClient)
+ .build();
+
+ final CloseableHttpResponse response = restClient.execute(request);
+
+ assertThat(response, notNullValue());
+ assertThat(response, sameInstance(mockResponse));
+ assertThat(response.getStatusLine(), sameInstance(HTTP_503_STATUSLINE));
+ }
+
+ @Test(expected = CloudstackRESTException.class)
+ public void testExecuteRequestWhenClientThrowsIOException() throws Exception {
+ final BasicRestClient restClient = BasicRestClient.create()
+ .host(LOCALHOST)
+ .client(HttpClientHelper.createHttpClient(5))
+ .build();
+
+ restClient.execute(request);
+ }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java b/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java
new file mode 100644
index 00000000000..20b32dd5a6e
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java
@@ -0,0 +1,38 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.Test;
+
+public class HttpClientHelperTest {
+
+ @Test
+ public void testCreateClient() throws Exception {
+ int maxRedirects = 5;
+ final CloseableHttpClient client = HttpClientHelper.createHttpClient(maxRedirects);
+
+ assertThat(client, notNullValue());
+ }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java b/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java
new file mode 100644
index 00000000000..effec792bcf
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java
@@ -0,0 +1,141 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import static org.mockito.Matchers.argThat;
+
+import java.io.IOException;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.ParseException;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.Description;
+import org.hamcrest.SelfDescribing;
+import org.mockito.ArgumentMatcher;
+
+public class HttpRequestMatcher extends ArgumentMatcher {
+ private final HttpRequest wanted;
+
+ public HttpRequestMatcher(final HttpRequest wanted) {
+ this.wanted = wanted;
+ }
+
+ public static HttpRequest eq(final HttpRequest request) {
+ return argThat(new HttpRequestMatcher(request));
+ }
+
+ @Override
+ public boolean matches(final Object actual) {
+ if (actual instanceof HttpUriRequest) {
+ final HttpUriRequest converted = (HttpUriRequest) actual;
+ return checkMethod(converted) && checkUri(converted) && checkPayload(converted);
+ } else {
+ return wanted == actual;
+ }
+ }
+
+ private boolean checkPayload(final HttpUriRequest actual) {
+ final String wantedPayload = getPayload(wanted);
+ final String actualPayload = getPayload(actual);
+ return equalsString(wantedPayload, actualPayload);
+ }
+
+ private static String getPayload(final HttpRequest request) {
+ String payload = "";
+ if (request instanceof HttpEntityEnclosingRequest) {
+ try {
+ payload = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity());
+ } catch (final ParseException e) {
+ throw new IllegalArgumentException("Couldn't read request's entity payload.", e);
+ } catch (final IOException e) {
+ throw new IllegalArgumentException("Couldn't read request's entity payload.", e);
+ }
+ }
+ return payload;
+ }
+
+ private boolean checkUri(final HttpUriRequest actual) {
+ if (wanted instanceof HttpUriRequest) {
+ final String wantedQuery = ((HttpUriRequest) wanted).getURI().getQuery();
+ final String actualQuery = actual.getURI().getQuery();
+ return equalsString(wantedQuery, actualQuery);
+ } else {
+ return wanted == actual;
+ }
+ }
+
+ private boolean checkMethod(final HttpUriRequest actual) {
+ if (wanted instanceof HttpUriRequest) {
+ final String wantedMethod = ((HttpUriRequest) wanted).getMethod();
+ final String actualMethod = actual.getMethod();
+ return equalsString(wantedMethod, actualMethod);
+ } else {
+ return wanted == actual;
+ }
+ }
+
+ private static boolean equalsString(final String a, final String b) {
+ return a == b || a != null && a.equals(b);
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(describe(wanted));
+ }
+
+ public String describe(final HttpRequest object) {
+ final StringBuilder sb = new StringBuilder();
+ if (object instanceof HttpUriRequest) {
+ final HttpUriRequest converted = (HttpUriRequest) object;
+ sb.append("method = ").append(converted.getMethod());
+ sb.append(", query = ").append(converted.getURI().getQuery());
+ sb.append(", payload = ").append(getPayload(object));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ return EqualsBuilder.reflectionEquals(this, o);
+ }
+
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ public SelfDescribing withExtraTypeInfo() {
+ return new SelfDescribing() {
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("(" + wanted.getClass().getSimpleName() + ") ").appendText(describe(wanted));
+ }
+ };
+ }
+
+ public boolean typeMatches(final Object object) {
+ return wanted != null && object != null && object.getClass() == wanted.getClass();
+ }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java
new file mode 100644
index 00000000000..470f563e909
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java
@@ -0,0 +1,115 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+import java.util.HashMap;
+
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.junit.Test;
+
+import com.google.common.base.Optional;
+
+public class HttpUriRequestBuilderTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildWithNullMethod() throws Exception {
+ HttpUriRequestBuilder.create().path("/path").build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildWithNullPath() throws Exception {
+ HttpUriRequestBuilder.create().method(HttpMethods.GET).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildWithEmptyPath() throws Exception {
+ HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("")
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBuildWithIlegalPath() throws Exception {
+ HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("path")
+ .build();
+ }
+
+ @Test
+ public void testBuildSimpleRequest() throws Exception {
+ final HttpUriRequest request = HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("/path")
+ .build();
+
+ assertThat(request, notNullValue());
+ assertThat(request.getURI().getPath(), equalTo("/path"));
+ assertThat(request.getURI().getScheme(), nullValue());
+ assertThat(request.getURI().getQuery(), nullValue());
+ assertThat(request.getURI().getHost(), nullValue());
+ assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME));
+ }
+
+ @Test
+ public void testBuildRequestWithParameters() throws Exception {
+ final HashMap parameters = new HashMap();
+ parameters.put("key1", "value1");
+ final HttpUriRequest request = HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("/path")
+ .parameters(parameters)
+ .build();
+
+ assertThat(request, notNullValue());
+ assertThat(request.getURI().getPath(), equalTo("/path"));
+ assertThat(request.getURI().getQuery(), equalTo("key1=value1"));
+ assertThat(request.getURI().getScheme(), nullValue());
+ assertThat(request.getURI().getHost(), nullValue());
+ assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME));
+ }
+
+ @Test
+ public void testBuildRequestWithJsonPayload() throws Exception {
+ final HttpUriRequest request = HttpUriRequestBuilder.create()
+ .method(HttpMethods.GET)
+ .path("/path")
+ .jsonPayload(Optional.of("{'key1':'value1'}"))
+ .build();
+
+ assertThat(request, notNullValue());
+ assertThat(request.getURI().getPath(), equalTo("/path"));
+ assertThat(request.getURI().getScheme(), nullValue());
+ assertThat(request.getURI().getQuery(), nullValue());
+ assertThat(request.getURI().getHost(), nullValue());
+ assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME));
+ assertThat(request.containsHeader(HttpConstants.CONTENT_TYPE), equalTo(true));
+ assertThat(request.getFirstHeader(HttpConstants.CONTENT_TYPE).getValue(), equalTo(HttpConstants.JSON_CONTENT_TYPE));
+ assertThat(request, HttpUriRequestPayloadMatcher.hasPayload("{'key1':'value1'}"));
+ }
+
+}
diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java
new file mode 100644
index 00000000000..e73641bf865
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java
@@ -0,0 +1,48 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+//
+
+package com.cloud.utils.rest;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.Matchers.argThat;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
+
+public class HttpUriRequestQueryMatcher extends FeatureMatcher {
+
+ public static HttpUriRequest aQuery(final String query) {
+ return argThat(new HttpUriRequestQueryMatcher(equalTo(query), "query", "query"));
+ }
+
+ public static HttpUriRequest aQueryThatContains(final String query) {
+ return argThat(new HttpUriRequestQueryMatcher(containsString(query), "query", "query"));
+ }
+
+ public HttpUriRequestQueryMatcher(final Matcher super String> subMatcher, final String featureDescription, final String featureName) {
+ super(subMatcher, featureDescription, featureName);
+ }
+
+ @Override
+ protected String featureValueOf(final HttpUriRequest actual) {
+ return actual.getURI().getQuery();
+ }
+}