Delegate HTTP protocol activity in RESTServiceConnector to RestClient

- All HTTP protocol activities are now handled by RestClient
- This service is now only responsible for creating requests, and
  dispatching them to the client
- Provides a Simple API for creating, updating, retrieving and deleting
  objects


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

Branch: refs/heads/deploy-from-snapshot
Commit: de63b94f2dc571e74ce5eaa68c74ed6a9edf420c
Parents: 8a93bb8
Author: Miguel Ferreira <miguelferre...@me.com>
Authored: Sun Aug 23 15:11:29 2015 +0200
Committer: Miguel Ferreira <miguelferre...@me.com>
Committed: Tue Aug 25 17:36:14 2015 +0200

----------------------------------------------------------------------
 .../BasicEncodedRESTValidationStrategy.java     |  66 ---
 .../cloud/utils/rest/RESTServiceConnector.java  | 391 +++----------
 .../utils/rest/RESTValidationStrategy.java      | 159 ------
 .../utils/rest/HttpUriRequestMethodMatcher.java |  44 ++
 .../rest/HttpUriRequestPayloadMatcher.java      |  62 +++
 .../utils/rest/RESTServiceConnectorTest.java    | 553 ++++++++-----------
 6 files changed, 430 insertions(+), 845 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/main/java/com/cloud/utils/rest/BasicEncodedRESTValidationStrategy.java
----------------------------------------------------------------------
diff --git 
a/utils/src/main/java/com/cloud/utils/rest/BasicEncodedRESTValidationStrategy.java
 
b/utils/src/main/java/com/cloud/utils/rest/BasicEncodedRESTValidationStrategy.java
deleted file mode 100644
index bf001cd..0000000
--- 
a/utils/src/main/java/com/cloud/utils/rest/BasicEncodedRESTValidationStrategy.java
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// 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 org.apache.commons.codec.binary.Base64;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpMethodBase;
-
-/**
- * Base 64 encoded authorization strategy. This implementation as opposed to
- * {@link RESTValidationStrategy} doesn't do a login after auth error, but 
instead
- * includes the encoded credentials in each request, instead of a cookie.
- */
-public class BasicEncodedRESTValidationStrategy extends RESTValidationStrategy 
{
-
-    public BasicEncodedRESTValidationStrategy(final String host, final String 
adminuser, final String adminpass) {
-        super();
-        this.host = host;
-        user = adminuser;
-        password = adminpass;
-    }
-
-    public BasicEncodedRESTValidationStrategy() {
-    }
-
-    @Override
-    public void executeMethod(final HttpMethodBase method, final HttpClient 
client,
-            final String protocol)
-                    throws CloudstackRESTException, HttpException, IOException 
{
-        if (host == null || host.isEmpty() || user == null || user.isEmpty() 
|| password == null || password.isEmpty()) {
-            throw new CloudstackRESTException("Hostname/credentials are null 
or empty");
-        }
-
-        final String encodedCredentials = encodeCredentials();
-        method.setRequestHeader("Authorization", "Basic " + 
encodedCredentials);
-        client.executeMethod(method);
-    }
-
-    private String encodeCredentials() {
-        final String authString = user + ":" + password;
-        final byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
-        final String authStringEnc = new String(authEncBytes);
-        return authStringEnc;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java
----------------------------------------------------------------------
diff --git a/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java 
b/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java
index 01aca89..fc5bae3 100644
--- a/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java
+++ b/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java
@@ -20,371 +20,146 @@
 package com.cloud.utils.rest;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Type;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
-import java.net.Socket;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
-import java.util.Map.Entry;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-import org.apache.cloudstack.utils.security.SSLUtils;
-import org.apache.cloudstack.utils.security.SecureSSLSocketFactory;
-import org.apache.commons.httpclient.ConnectTimeoutException;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.HttpMethodBase;
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
-import org.apache.commons.httpclient.NameValuePair;
-import org.apache.commons.httpclient.cookie.CookiePolicy;
-import org.apache.commons.httpclient.methods.DeleteMethod;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.httpclient.methods.StringRequestEntity;
-import org.apache.commons.httpclient.params.HttpConnectionParams;
-import org.apache.commons.httpclient.protocol.Protocol;
-import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
-import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.util.EntityUtils;
 import org.apache.log4j.Logger;
 
+import com.google.common.base.Optional;
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonDeserializer;
-import com.google.gson.reflect.TypeToken;
 
 /**
- * This abstraction encapsulates client side code for REST service 
communication. It encapsulates access in a delegate validation strategy. There 
may different implementations
- * extending {@link RESTValidationStrategy}, and any of them should mention 
the needed data to work.
+ * This abstraction encapsulates client side code for REST service 
communication. It encapsulates access in a REST client. There can be different 
implementations of that client
+ * implementing {@link RestClient}, and any of them should mention the needed 
data to work.
  *
- * This connector allows the use of {@link JsonDeserializer} for specific 
classes. You can provide in the constructor a list of classes and a list of 
deserializers for these
- * classes. These should be a correlated so that Nth deserializer is correctly 
mapped to Nth class.
+ * This connector allows the use of {@link JsonDeserializer} for specific 
classes. You can provide in the constructor a map of classes to deserializers 
for these classes.
  */
 public class RESTServiceConnector {
-    private static final String HTTPS = "https";
-    protected static final String GET_METHOD_TYPE = "get";
-    protected static final String DELETE_METHOD_TYPE = "delete";
-    protected static final String PUT_METHOD_TYPE = "put";
-    protected static final String POST_METHOD_TYPE = "post";
-    private static final String TEXT_HTML_CONTENT_TYPE = "text/html";
-    private static final String JSON_CONTENT_TYPE = "application/json";
-    private static final String CONTENT_TYPE = "Content-Type";
-    private static final int BODY_RESP_MAX_LEN = 1024;
-    private static final int HTTPS_PORT = 443;
 
     private static final Logger s_logger = 
Logger.getLogger(RESTServiceConnector.class);
 
-    protected final static String protocol = HTTPS;
-
-    private final static MultiThreadedHttpConnectionManager 
s_httpClientManager = new MultiThreadedHttpConnectionManager();
-
-    protected RESTValidationStrategy validation;
-
-    private final HttpClient client;
+    private static final Optional<String> ABSENT = Optional.absent();
 
+    private final RestClient client;
     private final Gson gson;
 
-    /**
-     * Getter that may be needed only for test purpose
-     *
-     * @return
-     */
-    public Gson getGson() {
-        return gson;
+    private RESTServiceConnector(final Builder builder) {
+        client = builder.client;
+        gson = setGsonDeserializer(builder.classToDeserializerMap);
     }
 
-    public RESTServiceConnector(final RESTValidationStrategy 
validationStrategy) {
-        this(validationStrategy, null, null);
-    }
-
-    public RESTServiceConnector(final RESTValidationStrategy 
validationStrategy, final List<Class<?>> classList, final 
List<JsonDeserializer<?>> deserializerList) {
-        validation = validationStrategy;
-        client = createHttpClient();
-        client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
-
-        try {
-            // Cast to ProtocolSocketFactory to avoid the deprecated 
constructor with the SecureProtocolSocketFactory parameter
-            Protocol.registerProtocol(HTTPS, new Protocol(HTTPS, 
(ProtocolSocketFactory) new TrustingProtocolSocketFactory(), HTTPS_PORT));
-        } catch (final IOException e) {
-            s_logger.warn("Failed to register the 
TrustingProtocolSocketFactory, falling back to default SSLSocketFactory", e);
-        }
-
+    private static Gson setGsonDeserializer(final Map<Class<?>, 
JsonDeserializer<?>> classToDeserializerMap) {
         final GsonBuilder gsonBuilder = new GsonBuilder();
-        if (classList != null && deserializerList != null) {
-            for (int i = 0; i < classList.size() && i < 
deserializerList.size(); i++) {
-                gsonBuilder.registerTypeAdapter(classList.get(i), 
deserializerList.get(i));
-            }
+        for (final Map.Entry<Class<?>, JsonDeserializer<?>> entry : 
classToDeserializerMap.entrySet()) {
+            gsonBuilder.registerTypeAdapter(entry.getKey(), entry.getValue());
         }
-        gson = 
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+        return 
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
     }
 
-    public HttpClient createHttpClient() {
-        return new HttpClient(s_httpClientManager);
+    public static Builder create() {
+        return new Builder();
     }
 
-    public HttpMethod createMethod(final String type, final String uri) throws 
CloudstackRESTException {
-        String url;
-        try {
-            url = new URL(protocol, validation.getHost(), uri).toString();
-        } catch (final MalformedURLException e) {
-            s_logger.error("Unable to build REST Service URL", e);
-            throw new CloudstackRESTException("Unable to build Nicira API 
URL", e);
-        }
-
-        if (POST_METHOD_TYPE.equalsIgnoreCase(type)) {
-            return new PostMethod(url);
-        } else if (GET_METHOD_TYPE.equalsIgnoreCase(type)) {
-            return new GetMethod(url);
-        } else if (DELETE_METHOD_TYPE.equalsIgnoreCase(type)) {
-            return new DeleteMethod(url);
-        } else if (PUT_METHOD_TYPE.equalsIgnoreCase(type)) {
-            return new PutMethod(url);
-        } else {
-            throw new CloudstackRESTException("Requesting unknown method 
type");
-        }
+    public <T> void executeUpdateObject(final T newObject, final String path, 
final Map<String, String> parameters) throws CloudstackRESTException {
+        s_logger.debug("Executing update object on " + path);
+        client.closeResponse(createAndExecuteRequest(HttpMethods.PUT, path, 
parameters, Optional.fromNullable(gson.toJson(newObject))));
     }
 
-    public void setControllerAddress(final String address) {
-        validation.setHost(address);
+    public <T> void executeUpdateObject(final T newObject, final String path) 
throws CloudstackRESTException {
+        executeUpdateObject(newObject, path, new HashMap<String, String>());
     }
 
-    public void setAdminCredentials(final String username, final String 
password) {
-        validation.setUser(username);
-        validation.setPassword(password);
+    @SuppressWarnings("unchecked")
+    public <T> T executeCreateObject(final T newObject, final String path, 
final Map<String, String> parameters) throws CloudstackRESTException {
+        s_logger.debug("Executing create object on " + path);
+        final CloseableHttpResponse response = 
createAndExecuteRequest(HttpMethods.POST, path, parameters, 
Optional.fromNullable(gson.toJson(newObject)));
+        return (T) readResponseBody(response, newObject.getClass());
     }
 
-    public <T> void executeUpdateObject(final T newObject, final String uri, 
final Map<String, String> parameters) throws CloudstackRESTException {
-
-        final PutMethod pm = (PutMethod) createMethod(PUT_METHOD_TYPE, uri);
-        pm.setRequestHeader(CONTENT_TYPE, JSON_CONTENT_TYPE);
-        try {
-            pm.setRequestEntity(new 
StringRequestEntity(gson.toJson(newObject), JSON_CONTENT_TYPE, null));
-        } catch (final UnsupportedEncodingException e) {
-            throw new CloudstackRESTException("Failed to encode json request 
body", e);
-        }
-
-        executeMethod(pm);
-
-        if (pm.getStatusCode() != HttpStatus.SC_OK) {
-            final String errorMessage = responseToErrorMessage(pm);
-            pm.releaseConnection();
-            s_logger.error("Failed to update object : " + errorMessage);
-            throw new CloudstackRESTException("Failed to update object : " + 
errorMessage);
-        }
-        pm.releaseConnection();
+    public <T> T executeCreateObject(final T newObject, final String uri) 
throws CloudstackRESTException {
+        return executeCreateObject(newObject, uri, new HashMap<String, 
String>());
     }
 
-    @SuppressWarnings("unchecked")
-    public <T> T executeCreateObject(final T newObject, final Type 
returnObjectType, final String uri, final Map<String, String> parameters) 
throws CloudstackRESTException {
-
-        final PostMethod pm = (PostMethod) createMethod(POST_METHOD_TYPE, uri);
-        pm.setRequestHeader(CONTENT_TYPE, JSON_CONTENT_TYPE);
-        try {
-            pm.setRequestEntity(new 
StringRequestEntity(gson.toJson(newObject), JSON_CONTENT_TYPE, null));
-        } catch (final UnsupportedEncodingException e) {
-            throw new CloudstackRESTException("Failed to encode json request 
body", e);
-        }
-
-        executeMethod(pm);
-
-        if (pm.getStatusCode() != HttpStatus.SC_CREATED) {
-            final String errorMessage = responseToErrorMessage(pm);
-            pm.releaseConnection();
-            s_logger.error("Failed to create object : " + errorMessage);
-            throw new CloudstackRESTException("Failed to create object : " + 
errorMessage);
-        }
-
-        T result;
-        try {
-            result = (T) gson.fromJson(pm.getResponseBodyAsString(), 
TypeToken.get(newObject.getClass()).getType());
-        } catch (final IOException e) {
-            throw new CloudstackRESTException("Failed to decode json response 
body", e);
-        } finally {
-            pm.releaseConnection();
-        }
-
-        return result;
+    public void executeDeleteObject(final String path) throws 
CloudstackRESTException {
+        s_logger.debug("Executing delete object on " + path);
+        client.closeResponse(createAndExecuteRequest(HttpMethods.DELETE, path, 
new HashMap<String, String>(), ABSENT));
     }
 
-    public void executeDeleteObject(final String uri) throws 
CloudstackRESTException {
-        final DeleteMethod dm = (DeleteMethod) 
createMethod(DELETE_METHOD_TYPE, uri);
-        dm.setRequestHeader(CONTENT_TYPE, JSON_CONTENT_TYPE);
-
-        executeMethod(dm);
-
-        if (dm.getStatusCode() != HttpStatus.SC_NO_CONTENT) {
-            final String errorMessage = responseToErrorMessage(dm);
-            dm.releaseConnection();
-            s_logger.error("Failed to delete object : " + errorMessage);
-            throw new CloudstackRESTException("Failed to delete object : " + 
errorMessage);
-        }
-        dm.releaseConnection();
+    public <T> T executeRetrieveObject(final Type returnObjectType, final 
String path, final Map<String, String> parameters) throws 
CloudstackRESTException {
+        s_logger.debug("Executing retrieve object on " + path);
+        final CloseableHttpResponse response = 
createAndExecuteRequest(HttpMethods.GET, path, parameters, ABSENT);
+        return readResponseBody(response, returnObjectType);
     }
 
-    @SuppressWarnings("unchecked")
-    public <T> T executeRetrieveObject(final Type returnObjectType, final 
String uri, final Map<String, String> parameters) throws 
CloudstackRESTException {
-        final GetMethod gm = (GetMethod) createMethod(GET_METHOD_TYPE, uri);
-        gm.setRequestHeader(CONTENT_TYPE, JSON_CONTENT_TYPE);
-        if (parameters != null && !parameters.isEmpty()) {
-            final List<NameValuePair> nameValuePairs = new 
ArrayList<NameValuePair>(parameters.size());
-            for (final Entry<String, String> e : parameters.entrySet()) {
-                nameValuePairs.add(new NameValuePair(e.getKey(), 
e.getValue()));
-            }
-            gm.setQueryString(nameValuePairs.toArray(new NameValuePair[0]));
-        }
-
-        executeMethod(gm);
+    public <T> T executeRetrieveObject(final Type returnObjectType, final 
String path) throws CloudstackRESTException {
+        return executeRetrieveObject(returnObjectType, path, new 
HashMap<String, String>());
+    }
 
-        if (gm.getStatusCode() != HttpStatus.SC_OK) {
-            final String errorMessage = responseToErrorMessage(gm);
-            gm.releaseConnection();
-            s_logger.error("Failed to retrieve object : " + errorMessage);
-            throw new CloudstackRESTException("Failed to retrieve object : " + 
errorMessage);
-        }
+    private CloseableHttpResponse createAndExecuteRequest(final HttpMethods 
method, final String path, final Map<String, String> parameters, final 
Optional<String> jsonPayLoad)
+                    throws CloudstackRESTException {
+        final HttpUriRequest httpRequest = HttpUriRequestBuilder.create()
+            .path(path)
+            .parameters(parameters)
+            .jsonPayload(jsonPayLoad)
+            .method(method)
+            .build();
+        if (jsonPayLoad.isPresent()) {
+            s_logger.debug("Built request '" + httpRequest + "' with payload: 
" + jsonPayLoad);
+        }
+        return executeRequest(httpRequest);
+    }
 
-        T returnValue;
-        try {
-            returnValue = (T) gson.fromJson(gm.getResponseBodyAsString(), 
returnObjectType);
-        } catch (final IOException e) {
-            s_logger.error("IOException while retrieving response body", e);
-            throw new CloudstackRESTException(e);
-        } finally {
-            gm.releaseConnection();
-        }
-        return returnValue;
+    private CloseableHttpResponse executeRequest(final HttpUriRequest 
httpRequest) throws CloudstackRESTException {
+        final CloseableHttpResponse response = client.execute(httpRequest);
+        s_logger.debug("Executed request: " + httpRequest + " status was " + 
response.getStatusLine().toString());
+        return response;
     }
 
-    public void executeMethod(final HttpMethodBase method) throws 
CloudstackRESTException {
+    private <T> T readResponseBody(final CloseableHttpResponse response, final 
Type type) throws CloudstackRESTException {
+        final HttpEntity entity = response.getEntity();
         try {
-            validation.executeMethod(method, client, protocol);
-        } catch (final HttpException e) {
-            s_logger.error("HttpException caught while trying to connect to 
the REST Service", e);
-            method.releaseConnection();
-            throw new CloudstackRESTException("API call to REST Service 
Failed", e);
+            final String stringEntity = EntityUtils.toString(entity);
+            s_logger.debug("Response entity: " + stringEntity);
+            EntityUtils.consumeQuietly(entity);
+            return gson.fromJson(stringEntity, type);
         } catch (final IOException e) {
-            s_logger.error("IOException caught while trying to connect to the 
REST Service", e);
-            method.releaseConnection();
-            throw new CloudstackRESTException("API call to Nicira REST Service 
Failed", e);
-        }
-    }
-
-    private static String responseToErrorMessage(final HttpMethodBase method) {
-        assert method.isRequestSent() : "no use getting an error message 
unless the request is sent";
-
-        if 
(TEXT_HTML_CONTENT_TYPE.equals(method.getResponseHeader(CONTENT_TYPE).getValue()))
 {
-            // The error message is the response content
-            // Safety margin of 1024 characters, anything longer is probably 
useless
-            // and will clutter the logs
-            try {
-                return method.getResponseBodyAsString(BODY_RESP_MAX_LEN);
-            } catch (final IOException e) {
-                s_logger.debug("Error while loading response body", e);
-            }
+            throw new CloudstackRESTException("Could not deserialize response 
to JSON. Entity: " + entity, e);
+        } finally {
+            client.closeResponse(response);
         }
 
-        // The default
-        return method.getStatusText();
     }
 
-    /*
-     * Some controllers use a self-signed certificate. The 
TrustingProtocolSocketFactory will accept any provided certificate when making 
an SSL connection to the SDN Manager
-     */
-    private class TrustingProtocolSocketFactory implements 
SecureProtocolSocketFactory {
-
-        private SSLSocketFactory ssf;
-
-        public TrustingProtocolSocketFactory() throws IOException {
-            // Create a trust manager that does not validate certificate chains
-            final TrustManager[] trustAllCerts = new TrustManager[] { new 
X509TrustManager() {
-                @Override
-                public X509Certificate[] getAcceptedIssuers() {
-                    return null;
-                }
-
-                @Override
-                public void checkClientTrusted(final X509Certificate[] certs, 
final String authType) {
-                    // Trust always
-                }
-
-                @Override
-                public void checkServerTrusted(final X509Certificate[] certs, 
final String authType) {
-                    // Trust always
-                }
-            } };
-
-            try {
-                // Install the all-trusting trust manager
-                final SSLContext sc = SSLUtils.getSSLContext();
-                sc.init(null, trustAllCerts, new java.security.SecureRandom());
-                ssf = new SecureSSLSocketFactory(sc);
-            } catch (final KeyManagementException e) {
-                throw new IOException(e);
-            } catch (final NoSuchAlgorithmException e) {
-                throw new IOException(e);
-            }
-        }
+    public static class Builder {
+        private RestClient client;
+        final private Map<Class<?>, JsonDeserializer<?>> 
classToDeserializerMap = new HashMap<Class<?>, JsonDeserializer<?>>();
 
-        @Override
-        public Socket createSocket(final String host, final int port) throws 
IOException {
-            final SSLSocket socket = (SSLSocket) ssf.createSocket(host, port);
-            
socket.setEnabledProtocols(SSLUtils.getSupportedProtocols(socket.getEnabledProtocols()));
-            return socket;
+        public Builder client(final RestClient client) {
+            this.client = client;
+            return this;
         }
 
-        @Override
-        public Socket createSocket(final String address, final int port, final 
InetAddress localAddress, final int localPort) throws IOException, 
UnknownHostException {
-            final Socket socket = ssf.createSocket(address, port, 
localAddress, localPort);
-            if (socket instanceof SSLSocket) {
-                ((SSLSocket) 
socket).setEnabledProtocols(SSLUtils.getSupportedProtocols(((SSLSocket) 
socket).getEnabledProtocols()));
-            }
-            return socket;
+        public Builder classToDeserializerMap(final Map<Class<?>, 
JsonDeserializer<?>> classToDeserializerMap) {
+            this.classToDeserializerMap.clear();
+            this.classToDeserializerMap.putAll(classToDeserializerMap);
+            return this;
         }
 
-        @Override
-        public Socket createSocket(final Socket socket, final String host, 
final int port, final boolean autoClose) throws IOException, 
UnknownHostException {
-            final Socket s = ssf.createSocket(socket, host, port, autoClose);
-            if (s instanceof SSLSocket) {
-                ((SSLSocket) 
s).setEnabledProtocols(SSLUtils.getSupportedProtocols(((SSLSocket) 
s).getEnabledProtocols()));
-            }
-            return s;
+        public Builder classToDeserializerEntry(final Class<?> clazz, final 
JsonDeserializer<?> deserializer) {
+            classToDeserializerMap.put(clazz, deserializer);
+            return this;
         }
 
-        @Override
-        public Socket createSocket(final String host, final int port, final 
InetAddress localAddress, final int localPort, final HttpConnectionParams 
params) throws IOException,
-                UnknownHostException, ConnectTimeoutException {
-            final int timeout = params.getConnectionTimeout();
-            if (timeout == 0) {
-                final Socket socket = createSocket(host, port, localAddress, 
localPort);
-                if (socket instanceof SSLSocket) {
-                    ((SSLSocket) 
socket).setEnabledProtocols(SSLUtils.getSupportedProtocols(((SSLSocket) 
socket).getEnabledProtocols()));
-                }
-                return socket;
-            } else {
-                final Socket s = ssf.createSocket();
-                if (s instanceof SSLSocket) {
-                    ((SSLSocket) 
s).setEnabledProtocols(SSLUtils.getSupportedProtocols(((SSLSocket) 
s).getEnabledProtocols()));
-                }
-                s.bind(new InetSocketAddress(localAddress, localPort));
-                s.connect(new InetSocketAddress(host, port), timeout);
-                return s;
-            }
+        public RESTServiceConnector build() {
+            return new RESTServiceConnector(this);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/main/java/com/cloud/utils/rest/RESTValidationStrategy.java
----------------------------------------------------------------------
diff --git 
a/utils/src/main/java/com/cloud/utils/rest/RESTValidationStrategy.java 
b/utils/src/main/java/com/cloud/utils/rest/RESTValidationStrategy.java
deleted file mode 100644
index 8ec920a..0000000
--- a/utils/src/main/java/com/cloud/utils/rest/RESTValidationStrategy.java
+++ /dev/null
@@ -1,159 +0,0 @@
-//
-// 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.MalformedURLException;
-import java.net.URL;
-
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpMethodBase;
-import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.log4j.Logger;
-
-/**
- * Basic authentication strategy. This strategy needs user and password for 
authentication.
- *
- * A login URL is needed which will be used for login and getting the cookie 
to be used in next requests. If an executeMethod request fails due to 
authorization it will try to
- * login, get the cookie and repeat the attempt to execute the method.
- */
-public class RESTValidationStrategy {
-
-    private static final Logger s_logger = 
Logger.getLogger(RESTValidationStrategy.class);
-
-    protected String host;
-    protected String user;
-    protected String password;
-    protected String serverVersion;
-    protected String loginUrl;
-
-    public RESTValidationStrategy(final String host, final String user, final 
String password, final String serverVersion, final String loginUrl) {
-        super();
-        this.host = host;
-        this.user = user;
-        this.password = password;
-        this.serverVersion = serverVersion;
-        this.loginUrl = loginUrl;
-    }
-
-    public RESTValidationStrategy(final String loginUrl) {
-        this.loginUrl = loginUrl;
-    }
-
-    public RESTValidationStrategy() {
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public void setUser(final String user) {
-        this.user = user;
-    }
-
-    public String getPassword() {
-        return password;
-    }
-
-    public void setPassword(final String password) {
-        this.password = password;
-    }
-
-    public String getLoginUrl() {
-        return loginUrl;
-    }
-
-    public void setLoginUrl(final String loginUrl) {
-        this.loginUrl = loginUrl;
-    }
-
-    public String getHost() {
-        return host;
-    }
-
-    public void setHost(final String host) {
-        this.host = host;
-    }
-
-    public void executeMethod(final HttpMethodBase method, final HttpClient 
client, final String protocol) throws CloudstackRESTException, HttpException, 
IOException {
-        if (host == null || host.isEmpty() || user == null || user.isEmpty() 
|| password == null || password.isEmpty()) {
-            throw new CloudstackRESTException("Hostname/credentials are null 
or empty");
-        }
-
-        client.executeMethod(method);
-        if (method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
-            method.releaseConnection();
-            // login and try again
-            login(protocol, client);
-            client.executeMethod(method);
-        }
-    }
-
-    /**
-     * Logs against the REST server. The cookie is stored in the 
<code>_authcookie<code> variable.
-     * <p>
-     * The method returns false if the login failed or the connection could 
not be made.
-     *
-     */
-    protected void login(final String protocol, final HttpClient client) 
throws CloudstackRESTException {
-        String url;
-
-        if (host == null || host.isEmpty() || user == null || user.isEmpty() 
|| password == null || password.isEmpty()) {
-            throw new CloudstackRESTException("Hostname/credentials are null 
or empty");
-        }
-
-        try {
-            url = new URL(protocol, host, loginUrl).toString();
-        } catch (final MalformedURLException e) {
-            s_logger.error("Unable to build Nicira API URL", e);
-            throw new CloudstackRESTException("Unable to build Nicira API 
URL", e);
-        }
-
-        final PostMethod pm = new PostMethod(url);
-        pm.addParameter("username", user);
-        pm.addParameter("password", password);
-
-        try {
-            client.executeMethod(pm);
-        } catch (final HttpException e) {
-            throw new CloudstackRESTException("REST Service API login failed 
", e);
-        } catch (final IOException e) {
-            throw new CloudstackRESTException("REST Service API login failed 
", e);
-        } finally {
-            pm.releaseConnection();
-        }
-
-        if (pm.getStatusCode() != HttpStatus.SC_OK) {
-            s_logger.error("REST Service API login failed : " + 
pm.getStatusText());
-            throw new CloudstackRESTException("REST Service API login failed " 
+ pm.getStatusText());
-        }
-
-        // Extract the version for later use
-        if (pm.getResponseHeader("Server") != null) {
-            serverVersion = pm.getResponseHeader("Server").getValue();
-            s_logger.debug("Server reports version " + serverVersion);
-        }
-
-        // Success; the cookie required for login is kept in _client
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestMethodMatcher.java
----------------------------------------------------------------------
diff --git 
a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestMethodMatcher.java 
b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestMethodMatcher.java
new file mode 100644
index 0000000..f2d091f
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestMethodMatcher.java
@@ -0,0 +1,44 @@
+//
+// 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.equalTo;
+import static org.mockito.Matchers.argThat;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
+
+public class HttpUriRequestMethodMatcher extends 
FeatureMatcher<HttpUriRequest, String> {
+
+    public static HttpUriRequest aMethod(final String method) {
+        return argThat(new HttpUriRequestMethodMatcher(equalTo(method), 
"method", "method"));
+    }
+
+    public HttpUriRequestMethodMatcher(final Matcher<? super String> 
subMatcher, final String featureDescription, final String featureName) {
+        super(subMatcher, featureDescription, featureName);
+    }
+
+    @Override
+    protected String featureValueOf(final HttpUriRequest actual) {
+        return actual.getMethod();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPayloadMatcher.java
----------------------------------------------------------------------
diff --git 
a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPayloadMatcher.java 
b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPayloadMatcher.java
new file mode 100644
index 0000000..724f495
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPayloadMatcher.java
@@ -0,0 +1,62 @@
+//
+// 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.equalTo;
+import static org.mockito.Matchers.argThat;
+
+import java.io.IOException;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.ParseException;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
+
+public class HttpUriRequestPayloadMatcher extends 
FeatureMatcher<HttpUriRequest, String> {
+
+    public static HttpUriRequest aPayload(final String payload) {
+        return argThat(hasPayload(payload));
+    }
+
+    public static HttpUriRequestPayloadMatcher hasPayload(final String 
payload) {
+        return new HttpUriRequestPayloadMatcher(equalTo(payload), "payload", 
"payload");
+    }
+
+    public HttpUriRequestPayloadMatcher(final Matcher<? super String> 
subMatcher, final String featureDescription, final String featureName) {
+        super(subMatcher, featureDescription, featureName);
+    }
+
+    @Override
+    protected String featureValueOf(final HttpUriRequest actual) {
+        String payload = "";
+        if (actual instanceof HttpEntityEnclosingRequest) {
+            try {
+                payload = EntityUtils.toString(((HttpEntityEnclosingRequest) 
actual).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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/de63b94f/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java
----------------------------------------------------------------------
diff --git 
a/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java 
b/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java
index b177fe1..e26ee6a 100644
--- a/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java
+++ b/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java
@@ -19,376 +19,305 @@
 
 package com.cloud.utils.rest;
 
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.io.IOException;
-import java.util.Collections;
-
-import org.apache.commons.httpclient.Header;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.methods.DeleteMethod;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.httpclient.params.HttpClientParams;
-import org.apache.http.HttpStatus;
-import org.junit.Before;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicStatusLine;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.type.CollectionType;
 import org.junit.Test;
 
-public class RESTServiceConnectorTest {
-    protected static final String UUID = "aaaa";
-    protected static final String UUID_JSON_RESPONSE = "{\"uuid\" : \"aaaa\"}";
-
-    RESTServiceConnector connector;
-    HttpClient client = mock(HttpClient.class);
-    HttpMethod method;
-    String type;
-    String uri;
-
-    @Before
-    public void setUp() {
-        final HttpClientParams hmp = mock(HttpClientParams.class);
-        when(client.getParams()).thenReturn(hmp);
-        connector = new RESTServiceConnector(null) {
-            @Override
-            public HttpClient createHttpClient() {
-                return client;
-            }
-
-            @Override
-            public HttpMethod createMethod(final String newType, final String 
newUri) {
-                type = newType;
-                uri = newUri;
-                return method;
-            }
-        };
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
 
-        connector.validation = new RESTValidationStrategy();
-        connector.setAdminCredentials("admin", "adminpass");
-        connector.setControllerAddress("localhost");
-    }
-
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteLoginWithoutHostname() throws 
CloudstackRESTException {
-        connector.setControllerAddress(null);
-        connector.validation.login(RESTServiceConnector.protocol, client);
+public class RESTServiceConnectorTest {
+    private static final BasicStatusLine HTTP_200_STATUS_LINE = new 
BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK");
+    private static final Gson gson = new 
GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+    private static final Map<String, String> DEFAULT_TEST_PARAMETERS = new 
HashMap<String, String>();
+    static {
+        DEFAULT_TEST_PARAMETERS.put("arg1", "val1");
+        DEFAULT_TEST_PARAMETERS.put("arg2", "val2");
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteLoginWithoutCredentials() throws 
CloudstackRESTException {
-        method = mock(PutMethod.class);
-        connector.setAdminCredentials(null, null);
-        connector.validation.login(RESTServiceConnector.protocol, client);
-    }
+    @Test
+    public void testExecuteUpdateObject() throws Exception {
+        final TestPojo newObject = new TestPojo();
+        newObject.setField("newValue");
+        final String newObjectJson = gson.toJson(newObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteUpdateObjectWithoutHostname() throws 
CloudstackRESTException {
-        method = mock(PutMethod.class);
-        connector.setControllerAddress(null);
-        connector.executeUpdateObject(new String(), "/", Collections.<String, 
String> emptyMap());
-    }
+        connector.executeUpdateObject(newObject, "/somepath");
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteUpdateObjectWithoutCredentials() throws 
CloudstackRESTException {
-        method = mock(PutMethod.class);
-        connector.setAdminCredentials(null, null);
-        connector.executeUpdateObject(new String(), "/", Collections.<String, 
String> emptyMap());
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("PUT"), any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestPayloadMatcher.aPayload(newObjectJson), 
any(HttpClientContext.class));
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteCreateObjectWithoutHostname() throws 
CloudstackRESTException {
-        method = mock(PostMethod.class);
-        connector.setControllerAddress(null);
-        connector.executeCreateObject(new String(), String.class, "/", 
Collections.<String, String> emptyMap());
-    }
+    @Test
+    public void testExecuteUpdateObjectWithParameters() throws Exception {
+        final TestPojo newObject = new TestPojo();
+        newObject.setField("newValue");
+        final String newObjectJson = gson.toJson(newObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteCreateObjectWithoutCredentials() throws 
CloudstackRESTException {
-        method = mock(PostMethod.class);
-        connector.setAdminCredentials(null, null);
-        connector.executeCreateObject(new String(), String.class, "/", 
Collections.<String, String> emptyMap());
-    }
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteDeleteObjectWithoutHostname() throws 
CloudstackRESTException {
-        method = mock(DeleteMethod.class);
-        connector.setControllerAddress(null);
-        connector.executeDeleteObject("/");
-    }
+        connector.executeUpdateObject(newObject, "/somepath", 
DEFAULT_TEST_PARAMETERS);
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteDeleteObjectWithoutCredentials() throws 
CloudstackRESTException {
-        method = mock(DeleteMethod.class);
-        connector.setAdminCredentials(null, null);
-        connector.executeDeleteObject("/");
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("PUT"), any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestPayloadMatcher.aPayload(newObjectJson), 
any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg2=val2"), 
any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg1=val1"), 
any(HttpClientContext.class));
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteRetrieveObjectWithoutHostname() throws 
CloudstackRESTException {
-        method = mock(GetMethod.class);
-        connector.setControllerAddress(null);
-        connector.executeRetrieveObject(String.class, "/", 
Collections.<String, String> emptyMap());
+    @Test
+    public void testExecuteCreateObject() throws Exception {
+        final TestPojo newObject = new TestPojo();
+        newObject.setField("newValue");
+        final String newObjectJson = gson.toJson(newObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getEntity()).thenReturn(new StringEntity(newObjectJson));
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
+
+        final TestPojo object = connector.executeCreateObject(newObject, 
"/somepath");
+
+        assertThat(object, notNullValue());
+        assertThat(object, equalTo(newObject));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("POST"), any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestPayloadMatcher.aPayload(newObjectJson), 
any(HttpClientContext.class));
+        verify(response).close();
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteRetrieveObjectWithoutCredentials() throws 
CloudstackRESTException {
-        method = mock(GetMethod.class);
-        connector.setAdminCredentials(null, null);
-        connector.executeRetrieveObject(String.class, "/", 
Collections.<String, String> emptyMap());
+    @Test
+    public void testExecuteCreateObjectWithParameters() throws Exception {
+        final TestPojo newObject = new TestPojo();
+        newObject.setField("newValue");
+        final String newObjectJson = gson.toJson(newObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getEntity()).thenReturn(new StringEntity(newObjectJson));
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
+
+        final TestPojo object = connector.executeCreateObject(newObject, 
"/somepath", DEFAULT_TEST_PARAMETERS);
+
+        assertThat(object, notNullValue());
+        assertThat(object, equalTo(newObject));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("POST"), any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestPayloadMatcher.aPayload(newObjectJson), 
any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg2=val2"), 
any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg1=val1"), 
any(HttpClientContext.class));
+        verify(response).close();
     }
 
     @Test
-    public void testExecuteMethod() throws CloudstackRESTException {
-        final GetMethod gm = mock(GetMethod.class);
+    public void testExecuteDeleteObject() throws Exception {
+        final HttpEntity entity = mock(HttpEntity.class);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getEntity()).thenReturn(entity);
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
 
-        when(gm.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-        connector.executeMethod(gm);
-        verify(gm, times(1)).getStatusCode();
-    }
+        connector.executeDeleteObject("/somepath");
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteMethodWithLogin() throws CloudstackRESTException, 
HttpException, IOException {
-        final GetMethod gm = mock(GetMethod.class);
-        when(client.executeMethod((HttpMethod) any())).thenThrow(new 
HttpException());
-        
when(gm.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED).thenReturn(HttpStatus.SC_UNAUTHORIZED);
-        connector.executeMethod(gm);
-        verify(gm, times(1)).getStatusCode();
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("DELETE"), any(HttpClientContext.class));
+        verify(response).close();
     }
 
-    /*
-     * Bit of a roundabout way to ensure that login is called after an un 
authorized result It not possible to properly mock login()
-     */
-    public void testExecuteMethodWithLoginSucced2ndAttempt() throws 
CloudstackRESTException {
-        // Prepare
-        final GetMethod gm = mock(GetMethod.class);
-        
when(gm.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED).thenReturn(HttpStatus.SC_UNAUTHORIZED);
-
-        final RESTValidationStrategy previousValidationStrategy = 
connector.validation;
-        connector.validation = new RESTValidationStrategy() {
-            @Override
-            protected void login(final String protocol, final HttpClient 
client) throws CloudstackRESTException {
-                // Do nothing
-            }
-        };
-        connector.setAdminCredentials("admin", "adminpass");
-        connector.setControllerAddress("localhost");
-
-        // Execute
-        connector.executeMethod(gm);
-        // Leave mock object as is was
-        connector.validation = previousValidationStrategy;
-
-        // Assert/verify
-        verify(gm, times(2)).getStatusCode();
+    @Test
+    public void testExecuteRetrieveObject() throws Exception {
+        final TestPojo existingObject = new TestPojo();
+        existingObject.setField("existingValue");
+        final String newObjectJson = gson.toJson(existingObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getEntity()).thenReturn(new StringEntity(newObjectJson));
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
+
+        final TestPojo object = 
connector.executeRetrieveObject(TestPojo.class, "/somepath");
+
+        assertThat(object, notNullValue());
+        assertThat(object, equalTo(existingObject));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("GET"), any(HttpClientContext.class));
+        verify(response).close();
     }
 
     @Test
-    public void testExecuteCreateObject() throws CloudstackRESTException, 
IOException {
-        JsonEntity ls = new JsonEntity();
-        method = mock(PostMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_CREATED);
-        when(method.getResponseBodyAsString()).thenReturn(UUID_JSON_RESPONSE);
-        ls = connector.executeCreateObject(ls, JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        assertTrue(UUID.equals(ls.getUuid()));
-        verify(method, times(1)).releaseConnection();
-
+    public void testExecuteRetrieveObjectWithParameters() throws Exception {
+        final TestPojo existingObject = new TestPojo();
+        existingObject.setField("existingValue");
+        final String newObjectJson = gson.toJson(existingObject);
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getEntity()).thenReturn(new StringEntity(newObjectJson));
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder().client(restClient).build();
+
+        final TestPojo object = 
connector.executeRetrieveObject(TestPojo.class, "/somepath", 
DEFAULT_TEST_PARAMETERS);
+
+        assertThat(object, notNullValue());
+        assertThat(object, equalTo(existingObject));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestMethodMatcher.aMethod("GET"), any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg2=val2"), 
any(HttpClientContext.class));
+        verify(httpClient).execute(any(HttpHost.class), 
HttpUriRequestQueryMatcher.aQueryThatContains("arg1=val1"), 
any(HttpClientContext.class));
+        verify(response).close();
+    }
+
+    @Test(expected = JsonParseException.class)
+    public void testCustomDeserializerTypeMismatch() throws Exception {
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        when(response.getEntity()).thenReturn(new 
StringEntity("[{somethig_not_type : \"WrongType\"}]"));
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder()
+            .client(restClient)
+            .classToDeserializerEntry(TestPojo.class, new 
TestPojoDeserializer())
+            .build();
+
+        connector.executeRetrieveObject(TestPojo.class, "/somepath");
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteCreateObjectFailure() throws 
CloudstackRESTException, IOException {
-        JsonEntity ls = new JsonEntity();
-        method = mock(PostMethod.class);
-        
when(method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        final Header header = mock(Header.class);
-        when(header.getValue()).thenReturn("text/html");
-        when(method.getResponseHeader("Content-Type")).thenReturn(header);
-        when(method.getResponseBodyAsString()).thenReturn("Off to timbuktu, 
won't be back later.");
-        when(method.isRequestSent()).thenReturn(true);
-        try {
-            ls = connector.executeCreateObject(ls, JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+    @Test
+    public void testCustomDeserializerForCustomLists() throws Exception {
+        final CloseableHttpResponse response = 
mock(CloseableHttpResponse.class);
+        when(response.getStatusLine()).thenReturn(HTTP_200_STATUS_LINE);
+        when(response.getEntity()).thenReturn(new StringEntity("{results: 
[{field : \"SomeValue\"}], results_count: 1}"));
+        final CloseableHttpClient httpClient = mock(CloseableHttpClient.class);
+        when(httpClient.execute(any(HttpHost.class), any(HttpRequest.class), 
any(HttpClientContext.class))).thenReturn(response);
+        final RestClient restClient = new BasicRestClient(httpClient, 
HttpClientContext.create(), "localhost");
+        final Class<? extends CollectionType> clazzListOfTestPojo = new 
ObjectMapper().getTypeFactory().constructCollectionType(List.class, 
TestPojo.class).getClass();
+        final RESTServiceConnector connector = new 
RESTServiceConnector.Builder()
+            .client(restClient)
+            .classToDeserializerEntry(clazzListOfTestPojo, new 
CustomListDeserializer<TestPojoDeserializer>())
+            .build();
+
+        connector.executeRetrieveObject(TestPojo.class, "/somepath");
+    }
+
+    class NiciraList<T> {
+        private List<T> results;
+        private int resultCount;
+
+        public List<T> getResults() {
+            return results;
         }
-    }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteCreateObjectException() throws 
CloudstackRESTException, IOException {
-        JsonEntity ls = new JsonEntity();
-        when(client.executeMethod((HttpMethod) any())).thenThrow(new 
HttpException());
-        method = mock(PostMethod.class);
-        
when(method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        final Header header = mock(Header.class);
-        when(header.getValue()).thenReturn("text/html");
-        when(method.getResponseHeader("Content-Type")).thenReturn(header);
-        when(method.getResponseBodyAsString()).thenReturn("Off to timbuktu, 
won't be back later.");
-        try {
-            ls = connector.executeCreateObject(ls, JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        public void setResults(final List<T> results) {
+            this.results = results;
         }
-    }
-
-    @Test
-    public void testExecuteUpdateObject() throws CloudstackRESTException, 
IOException {
-        final JsonEntity ls = new JsonEntity();
-        method = mock(PutMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-        connector.executeUpdateObject(ls, "/", Collections.<String, String> 
emptyMap());
-        verify(method, times(1)).releaseConnection();
-        verify(client, times(1)).executeMethod(method);
-    }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteUpdateObjectFailure() throws 
CloudstackRESTException, IOException {
-        final JsonEntity ls = new JsonEntity();
-        method = mock(PutMethod.class);
-        
when(method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        final Header header = mock(Header.class);
-        when(header.getValue()).thenReturn("text/html");
-        when(method.getResponseHeader("Content-Type")).thenReturn(header);
-        when(method.getResponseBodyAsString()).thenReturn("Off to timbuktu, 
won't be back later.");
-        when(method.isRequestSent()).thenReturn(true);
-        try {
-            connector.executeUpdateObject(ls, "/", Collections.<String, 
String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        public int getResultCount() {
+            return resultCount;
         }
-    }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteUpdateObjectException() throws 
CloudstackRESTException, IOException {
-        final JsonEntity ls = new JsonEntity();
-        method = mock(PutMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-        when(client.executeMethod((HttpMethod) any())).thenThrow(new 
IOException());
-        try {
-            connector.executeUpdateObject(ls, "/", Collections.<String, 
String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        public void setResultCount(final int resultCount) {
+            this.resultCount = resultCount;
         }
-    }
 
-    @Test
-    public void testExecuteDeleteObject() throws CloudstackRESTException, 
IOException {
-        method = mock(DeleteMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT);
-        connector.executeDeleteObject("/");
-        verify(method, times(1)).releaseConnection();
-        verify(client, times(1)).executeMethod(method);
     }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteDeleteObjectFailure() throws 
CloudstackRESTException, IOException {
-        method = mock(DeleteMethod.class);
-        
when(method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        final Header header = mock(Header.class);
-        when(header.getValue()).thenReturn("text/html");
-        when(method.getResponseHeader("Content-Type")).thenReturn(header);
-        when(method.getResponseBodyAsString()).thenReturn("Off to timbuktu, 
won't be back later.");
-        when(method.isRequestSent()).thenReturn(true);
-        try {
-            connector.executeDeleteObject("/");
-        } finally {
-            verify(method, times(1)).releaseConnection();
-        }
-    }
+    class TestPojo {
+        private String field;
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteDeleteObjectException() throws 
CloudstackRESTException, IOException {
-        method = mock(DeleteMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT);
-        when(client.executeMethod((HttpMethod) any())).thenThrow(new 
HttpException());
-        try {
-            connector.executeDeleteObject("/");
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        public String getField() {
+            return field;
         }
-    }
-
-    @Test
-    public void testExecuteRetrieveObject() throws CloudstackRESTException, 
IOException {
-        method = mock(GetMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-        when(method.getResponseBodyAsString()).thenReturn(UUID_JSON_RESPONSE);
-        connector.executeRetrieveObject(JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        verify(method, times(1)).releaseConnection();
-        verify(client, times(1)).executeMethod(method);
-    }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteRetrieveObjectFailure() throws 
CloudstackRESTException, IOException {
-        method = mock(GetMethod.class);
-        
when(method.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-        when(method.getResponseBodyAsString()).thenReturn(UUID_JSON_RESPONSE);
-        final Header header = mock(Header.class);
-        when(header.getValue()).thenReturn("text/html");
-        when(method.getResponseHeader("Content-Type")).thenReturn(header);
-        when(method.getResponseBodyAsString()).thenReturn("Off to timbuktu, 
won't be back later.");
-        when(method.isRequestSent()).thenReturn(true);
-        try {
-            connector.executeRetrieveObject(JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        public void setField(final String field) {
+            this.field = field;
         }
-    }
 
-    @Test(expected = CloudstackRESTException.class)
-    public void testExecuteRetrieveObjectException() throws 
CloudstackRESTException, IOException {
-        method = mock(GetMethod.class);
-        when(method.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-        when(method.getResponseBodyAsString()).thenReturn(UUID_JSON_RESPONSE);
-        when(client.executeMethod((HttpMethod) any())).thenThrow(new 
HttpException());
-        try {
-            connector.executeRetrieveObject(JsonEntity.class, "/", 
Collections.<String, String> emptyMap());
-        } finally {
-            verify(method, times(1)).releaseConnection();
+        @Override
+        public int hashCode() {
+            return HashCodeBuilder.reflectionHashCode(this);
         }
-    }
 
-}
-
-class JsonEntity {
-    private String displayName;
-    private String uuid;
-    private String href;
-    private String schema;
+        @Override
+        public boolean equals(final Object obj) {
+            return EqualsBuilder.reflectionEquals(this, obj);
+        }
 
-    public String getDisplayName() {
-        return displayName;
     }
 
-    public void setDisplayName(final String displayName) {
-        this.displayName = displayName;
-    }
+    private final class TestPojoDeserializer implements 
JsonDeserializer<TestPojo> {
+        @Override
+        public TestPojo deserialize(final JsonElement json, final Type 
typeOfT, final JsonDeserializationContext context) throws JsonParseException {
+            final JsonObject jsonObject = json.getAsJsonObject();
 
-    public String getUuid() {
-        return uuid;
-    }
+            if (!jsonObject.has("type")) {
+                throw new JsonParseException("Deserializing as a TestPojo, but 
no type present in the json object");
+            }
 
-    public void setUuid(final String uuid) {
-        this.uuid = uuid;
+            return context.deserialize(jsonObject, TestPojo.class);
+        }
     }
 
-    public String getHref() {
-        return href;
-    }
+    private final class CustomListDeserializer<T> implements 
JsonDeserializer<T> {
+        private final Gson standardGson = new GsonBuilder().create();
 
-    public void setHref(final String href) {
-        this.href = href;
-    }
+        @Override
+        public T deserialize(final JsonElement json, final Type typeOfT, final 
JsonDeserializationContext context) throws JsonParseException {
+            final JsonObject jsonObject = json.getAsJsonObject();
 
-    public String getSchema() {
-        return schema;
-    }
+            System.err.println(json.toString());
 
-    public void setSchema(final String schema) {
-        this.schema = schema;
+            if (jsonObject.has("results")) {
+                final JsonArray results = jsonObject.getAsJsonArray("results");
+                return context.deserialize(results, typeOfT);
+            } else {
+                return standardGson.fromJson(jsonObject, typeOfT);
+            }
+        }
     }
-}
\ No newline at end of file
+}

Reply via email to