Add basic RestClient implentation based on HTTP Components 4.5

- Upgrade version of HTTP Components to 4.5
- Add helper class to create Http clients
- Add helper class to build http requests
- Add enum with the different Http Methods
- Add constants class for HTTP related values


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/8a93bb8d
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/8a93bb8d
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/8a93bb8d

Branch: refs/heads/deploy-from-snapshot
Commit: 8a93bb8d2dc85dc6382e373744f8e7c6a9593fb1
Parents: 6e74ef8
Author: Miguel Ferreira <miguelferre...@me.com>
Authored: Fri Aug 21 17:44:57 2015 +0200
Committer: Miguel Ferreira <miguelferre...@me.com>
Committed: Tue Aug 25 17:36:13 2015 +0200

----------------------------------------------------------------------
 pom.xml                                         |   2 +-
 .../com/cloud/utils/rest/BasicRestClient.java   | 118 ++++++++++++++++
 .../com/cloud/utils/rest/HttpClientHelper.java  |  71 ++++++++++
 .../com/cloud/utils/rest/HttpConstants.java     |  34 +++++
 .../java/com/cloud/utils/rest/HttpMethods.java  |  41 ++++++
 .../cloud/utils/rest/HttpUriRequestBuilder.java | 119 ++++++++++++++++
 .../java/com/cloud/utils/rest/RestClient.java   |  31 ++++
 .../cloud/utils/rest/BasicRestClientTest.java   | 106 ++++++++++++++
 .../cloud/utils/rest/HttpClientHelperTest.java  |  38 +++++
 .../cloud/utils/rest/HttpRequestMatcher.java    | 141 +++++++++++++++++++
 .../utils/rest/HttpUriRequestBuilderTest.java   | 115 +++++++++++++++
 .../utils/rest/HttpUriRequestQueryMatcher.java  |  48 +++++++
 12 files changed, 863 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 95b90db..b5e380e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
     <cs.guava-testlib.version>18.0</cs.guava-testlib.version>
     <cs.guava.version>18.0</cs.guava.version>
     <cs.xapi.version>6.2.0-3.1</cs.xapi.version>
-    <cs.httpclient.version>4.3.6</cs.httpclient.version>
+    <cs.httpclient.version>4.5</cs.httpclient.version>
     <cs.httpcore.version>4.4</cs.httpcore.version>
     <cs.commons-httpclient.version>3.1</cs.commons-httpclient.version>
     <cs.mysql.version>5.1.34</cs.mysql.version>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java
----------------------------------------------------------------------
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 0000000..5c29155
--- /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<T extends 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);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java
----------------------------------------------------------------------
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 0000000..1c6f564
--- /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<ConnectionSocketFactory> 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<ConnectionSocketFactory> 
createSocketFactoryConfigration() throws KeyManagementException, 
NoSuchAlgorithmException, KeyStoreException {
+        Registry<ConnectionSocketFactory> socketFactoryRegistry;
+        final SSLContext sslContext = 
SSLContexts.custom().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
+        final SSLConnectionSocketFactory cnnectionSocketFactory = new 
SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
+        socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> 
create()
+            .register(HTTPS, cnnectionSocketFactory)
+            .build();
+
+        return socketFactoryRegistry;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java
----------------------------------------------------------------------
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 0000000..93502a5
--- /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";
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java
----------------------------------------------------------------------
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 0000000..7fecad0
--- /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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java
----------------------------------------------------------------------
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 0000000..4fb3a1d
--- /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<String> ABSENT = Optional.absent();
+
+    private HttpMethods method;
+    private String path;
+    private Optional<String> jsonPayload = ABSENT;
+    private final Map<String, String> parameters = new HashMap<String, 
String>();
+    private final Map<String, String> methodParameters = new HashMap<String, 
String>();
+
+    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<String> 
jsonPayload) {
+        this.jsonPayload = jsonPayload;
+        return this;
+    }
+
+    public HttpUriRequestBuilder parameters(final Map<String, String> 
parameters) {
+        this.parameters.clear();
+        this.parameters.putAll(parameters);
+        return this;
+    }
+
+    public HttpUriRequestBuilder methodParameters(final Map<String, String> 
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<String, String> 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<String, String> 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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/RestClient.java
----------------------------------------------------------------------
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 0000000..104f09b
--- /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;
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java
----------------------------------------------------------------------
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 0000000..c30103e
--- /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);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java
----------------------------------------------------------------------
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 0000000..20b32dd
--- /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());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java
----------------------------------------------------------------------
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 0000000..effec79
--- /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<HttpRequest> {
+    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();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java
----------------------------------------------------------------------
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 0000000..470f563
--- /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<String, String> parameters = new HashMap<String, 
String>();
+        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'}"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java
----------------------------------------------------------------------
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 0000000..e73641b
--- /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<HttpUriRequest, 
String> {
+
+    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();
+    }
+}

Reply via email to