com.cloudmine.api.rest.CMWebService.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudmine.api.rest.CMWebService.java

Source

package com.cloudmine.api.rest;

import com.cloudmine.api.CMApiCredentials;
import com.cloudmine.api.CMChannel;
import com.cloudmine.api.CMFile;
import com.cloudmine.api.CMObject;
import com.cloudmine.api.CMPushNotification;
import com.cloudmine.api.CMSessionToken;
import com.cloudmine.api.JavaCMUser;
import com.cloudmine.api.JavaCMUser;
import com.cloudmine.api.LibrarySpecificClassCreator;
import com.cloudmine.api.Strings;
import com.cloudmine.api.exceptions.ConversionException;
import com.cloudmine.api.exceptions.CreationException;
import com.cloudmine.api.exceptions.NetworkException;
import com.cloudmine.api.persistance.ClassNameRegistry;
import com.cloudmine.api.rest.callbacks.CMCallback;
import com.cloudmine.api.rest.callbacks.CMResponseCallback;
import com.cloudmine.api.rest.callbacks.Callback;
import com.cloudmine.api.rest.callbacks.CreationResponseCallback;
import com.cloudmine.api.rest.options.CMRequestOptions;
import com.cloudmine.api.rest.response.CMObjectResponse;
import com.cloudmine.api.rest.response.CMResponse;
import com.cloudmine.api.rest.response.CMSocialLoginResponse;
import com.cloudmine.api.rest.response.CreationResponse;
import com.cloudmine.api.rest.response.FileCreationResponse;
import com.cloudmine.api.rest.response.FileLoadResponse;
import com.cloudmine.api.rest.response.ListOfValuesResponse;
import com.cloudmine.api.rest.response.LoginResponse;
import com.cloudmine.api.rest.response.ObjectModificationResponse;
import com.cloudmine.api.rest.response.PushChannelResponse;
import com.cloudmine.api.rest.response.ResponseBase;
import com.cloudmine.api.rest.response.ResponseConstructor;
import com.cloudmine.api.rest.response.TokenUpdateResponse;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.AbstractHttpMessage;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * Provides direct access to the CloudMine API. Useful if you don't need all the bookkeeping of a {@link CMStore}. Also
 * provides some synchronous implementations of API calls, for when you know you are not on the UI thread.
 * This base class performs all operations at the Application level. To perform operations at the User level, use a
 * {@link UserCMWebService}<br>
 * Preconditions for use:<br>
 * {@link com.cloudmine.api.BaseDeviceIdentifier#initialize(android.content.Context)} has been called with the activity context, if developing on Android<br>
 * {@link CMApiCredentials#initialize(String, String)} has been called with the application identifier and API key<br>
    
 * <br>Copyright CloudMine LLC. All rights reserved<br> See LICENSE file included with SDK for details.
 */
public class CMWebService {
    private static final Object singletonLock = new Object();
    private static CMWebService service;
    private static final Logger LOG = LoggerFactory.getLogger(CMWebService.class);
    public static final Header JSON_HEADER = new BasicHeader("Content-Type", "application/json");

    public static final String PASSWORD_KEY = "password";
    public static final String JSON_ENCODING = "UTF-8";
    public static final String AUTHORIZATION_KEY = "Authorization";

    public static final String EMAIL_KEY = "email";

    protected final CMURLBuilder baseUrl;
    private final HttpClient httpClient = new DefaultHttpClient();
    protected final AsynchronousHttpClient asyncHttpClient; //TODO split this into an asynch and synch impl instead of both in one?
    private CMSessionToken loggedInSessionToken;
    private final Map<CMSessionToken, UserCMWebService> loggedInUserServices = new WeakHashMap<CMSessionToken, UserCMWebService>();
    final String apiKey;

    /**
     * Get the instance of CMWebService. You should use this method instead of constructing your own,
     * as the actual implementation of CMWebService differs based on what platform your code is running
     * on.
     * @return a platform appropriate implementation of CMWebService
     * @throws CreationException if CMApiCredentials.initialize has not yet been called
     */
    public static CMWebService getService() throws CreationException {
        if (service == null) {
            synchronized (singletonLock) {
                if (service == null) {
                    service = new CMWebService(CMApiCredentials.getApplicationIdentifier(),
                            LibrarySpecificClassCreator.getCreator().getAsynchronousHttpClient());
                }
            }
        }
        return service;
    }

    public static CMWebService getService(CMApiCredentials credentials) {
        return getService(credentials.getIdentifier(), credentials.getApiKey(), credentials.getBaseUrl());
    }

    public static CMWebService getService(String appId, String apiKey) {
        return getService(appId, apiKey, CMApiCredentials.getCredentials().getBaseUrl());
    }

    public static CMWebService getService(String appId, String apiKey, String baseUrl) {
        CMApiCredentials.initialize(appId, apiKey, baseUrl);
        service = new CMWebService(CMApiCredentials.getApplicationIdentifier(),
                LibrarySpecificClassCreator.getCreator().getAsynchronousHttpClient());
        return service;
    }

    protected CMWebService(CMURLBuilder baseUrl, String apiKey, AsynchronousHttpClient asyncClient) {
        this.baseUrl = baseUrl;
        asyncHttpClient = asyncClient;
        this.apiKey = apiKey;
    }

    protected CMWebService(String appId, AsynchronousHttpClient asyncClient) {
        this(new CMURLBuilder(CMApiCredentials.getCredentials().getBaseUrl(), appId),
                CMApiCredentials.getApplicationApiKey(), asyncClient);
    }

    /**
     * If the entity response is not fully consumed, the connection will not be released, so once you're
     * done with an HttpResponse you need to consume the rest of the content
     * @param response the response to consume
     */
    public static void consumeEntityResponse(HttpResponse response) {
        if (response != null && response.getEntity() != null) {
            HttpEntity body = response.getEntity();
            if (body.isStreaming()) {
                try {
                    InputStream instream = body.getContent();
                    if (instream != null) {
                        instream.close();
                    }
                } catch (IOException e) {
                    //GNF
                }
            }
        }
    }

    /**
     * Get a UserCMWebService for the given token; if none already exist, one will be created. This lets
     * you perform operations at the user level, such as object persistance
     * @param token a session token return from a valid login request or a logged in user
     * @return A UserCMWebService that includes the given session token in its requests and operates at the user level
     * @throws CreationException if given a null or failed session token
     */
    public UserCMWebService getUserWebService(CMSessionToken token) throws CreationException {
        return createUserCMWebService(token);
    }

    protected UserCMWebService createUserCMWebService(CMSessionToken token) throws CreationException {
        if (token == null || CMSessionToken.FAILED.equals(token)) {
            throw new CreationException("Cannot create a UserCMWebService off a failed or null token");
        }
        UserCMWebService userService = loggedInUserServices.get(token);
        if (userService == null) {
            userService = UserCMWebService.UserCMWebService(baseUrl.copy().user(), apiKey, token, asyncHttpClient);
            loggedInUserServices.put(token, userService);
        }
        return userService;
    }

    /**
     * This will set the default UserCMWebService and return it. This must be called before calling
     * userWebService, unless you pass userWebService a CMSessionToken.
     * @param token the token retrieved from a LoginResponse
     * @return the UserCMWebService that is created from this request.
     */
    public synchronized UserCMWebService setLoggedInUser(CMSessionToken token) throws CreationException {
        loggedInSessionToken = token;
        return getUserWebService(token);
    }

    /**
     * Get the 'default' user web service. Requires that setUser has been called
     * @return the UserCMWebService for the logged in user
     * @throws CreationException if setUser has not been called, or was called with an invalid value
     */
    public synchronized UserCMWebService getUserWebService() throws CreationException {
        if (loggedInSessionToken == null) {
            throw new CreationException("Cannot request a user web service until setUser has been called");
        }
        return getUserWebService(loggedInSessionToken);
    }

    /**
     * Delete all objects. Be careful...
     * @return the ObjectModificationResponse containing information about what was deleted
     * @throws NetworkException if unable to perform the request
     */
    public ObjectModificationResponse deleteAll() throws NetworkException {
        return executeCommand(createDeleteAll(), objectModificationResponseConstructor());
    }

    /**
     * Delete the specified objects, based on their object id
     * @param objectIds the object ids of the objects to delete
     * @return the ObjectModificationResponse containing information about what was deleted
     * @throws NetworkException if unable to perform the request
     */
    public ObjectModificationResponse delete(Collection<String> objectIds) throws NetworkException {
        return executeCommand(createDelete(objectIds), objectModificationResponseConstructor());
    }

    /**
     * Delete the specified object, based on its object id
     * @param objectId the object id of the object to delete
     * @return the ObjectModificationResponse containing information about what was deleted
     * @throws NetworkException if unable to perform the request
     */
    public ObjectModificationResponse delete(String objectId) throws NetworkException {
        return executeCommand(createDelete(objectId), objectModificationResponseConstructor());
    }

    /**
     * Load all of the objects of the specified class
     * @param klass the class of the objects to load; this is either inferred directly or you can override {@link com.cloudmine.api.CMObject#getClassName}
     */
    public void asyncLoadObjectsOfClass(String klass) {
        asyncLoadObjectsOfClass(klass, CMCallback.<CMObjectResponse>doNothing());
    }

    /**
     * Load all of the objects of the specified class
     * @param klass the class of the objects to load; this is either inferred directly or you can override {@link com.cloudmine.api.CMObject#getClassName}
     * @param callback
     */
    public void asyncLoadObjectsOfClass(String klass, Callback<CMObjectResponse> callback) {
        asyncLoadObjectsOfClass(klass, callback, CMRequestOptions.NONE);
    }

    /**
     * Retrieve all the objects that are of the specified class. Class values are either inferred directly or you can override {@link com.cloudmine.api.CMObject#getClassName}
     * @param klass the class type to load
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     */
    public void asyncLoadObjectsOfClass(String klass, Callback<CMObjectResponse> callback,
            CMRequestOptions options) {
        HttpGet search = createSearch("[" + getClassSearchString(klass) + "]", options);
        executeAsyncCommand(search, callback, cmObjectResponseConstructor());

    }

    public void asyncLoadObjectsOfClass(Class<? extends CMObject> klass, Callback<CMObjectResponse> callback) {
        asyncLoadObjectsOfClass(klass, callback, CMRequestOptions.NONE);
    }

    public void asyncLoadObjectsOfClass(Class<? extends CMObject> klass, Callback<CMObjectResponse> callback,
            CMRequestOptions options) {
        HttpGet search = createSearch("[" + getClassSearchString(klass) + "]", options);
        executeAsyncCommand(search, callback, cmObjectResponseConstructor());
    }

    public void asyncLoadObjectsOfClassAndSearch(Class<? extends CMObject> klass, String search,
            Callback<CMObjectResponse> callback) {
        asyncLoadObjectsOfClassAndSearch(klass, search, callback, CMRequestOptions.NONE);
    }

    public void asyncLoadObjectsOfClassAndSearch(Class<? extends CMObject> klass, String search,
            Callback<CMObjectResponse> callback, CMRequestOptions options) {
        executeAsyncCommand(createSearch(addClassSearch(klass, search), options), callback,
                cmObjectResponseConstructor());

    }

    private String getClassSearchString(Class<? extends CMObject> klass) {
        String className = ClassNameRegistry.forClass(klass);
        return getClassSearchString(className);
    }

    private String getClassSearchString(String className) {
        return JsonUtilities.CLASS_KEY + "=" + JsonUtilities.addQuotes(className);
    }

    private String addClassSearch(Class<? extends CMObject> klass, String search) {
        int endOfSearch = search.lastIndexOf("]");
        if (endOfSearch == -1) {
            return search; //this is an invalid search which will fail server side
        }
        String openSearch = search.substring(0, endOfSearch);
        openSearch += ", " + getClassSearchString(klass) + "]";
        return openSearch;
    }

    public void asyncLoadAllUserProfiles(Callback<CMObjectResponse> callback) {
        executeAsyncCommand(createGetAllUsers(), callback, cmObjectResponseConstructor());
    }

    public void asyncSearchUserProfiles(String searchString, Callback<CMObjectResponse> callback) {
        asyncSearchUserProfiles(searchString, CMRequestOptions.NONE, callback);
    }

    public void asyncSearchUserProfiles(String searchString, CMRequestOptions options,
            Callback<CMObjectResponse> callback) {
        executeAsyncCommand(createProfileSearch(searchString, options), callback, cmObjectResponseConstructor());
    }

    /**
     * Delete the given object from CloudMine.
     * @param object to delete; this is done based on the object id, its values are ignored
     */
    public void asyncDeleteObject(CMObject object) {
        asyncDeleteObject(object, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the given object from CloudMine.
     * @param object to delete; this is done based on the object id, its values are ignored
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void asyncDeleteObject(CMObject object, Callback<ObjectModificationResponse> callback) {
        asyncDeleteObject(object, callback, CMRequestOptions.NONE);
    }

    /**
     * Delete the given object from CloudMine.
     * @param object to delete; this is done based on the object id, its values are ignored
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncDeleteObject(CMObject object, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) {
        asyncDeleteObjects(Collections.singletonList(object), callback, options);
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objects to delete; this is done based on the object ids, values are ignored
     */
    public void asyncDeleteObjects(Collection<? extends CMObject> objects) {
        asyncDeleteObjects(objects, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objects to delete; this is done based on the object ids, values are ignored
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void asyncDeleteObjects(Collection<? extends CMObject> objects,
            Callback<ObjectModificationResponse> callback) {
        asyncDeleteObjects(objects, callback, CMRequestOptions.NONE);
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objects to delete; this is done based on the object ids, values are ignored
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncDeleteObjects(Collection<? extends CMObject> objects,
            Callback<ObjectModificationResponse> callback, CMRequestOptions options) {
        int size = objects.size();
        Collection<String> keys = new ArrayList<String>(size);
        for (CMObject object : objects) {
            keys.add(object.getObjectId());
        }
        asyncDelete(keys, callback, options);
    }

    /**
     * Delete the given object from CloudMine.
     * @param objectId to delete; this is done based on the object id
     */
    public void asyncDelete(String objectId) {
        asyncDelete(objectId, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the given object from CloudMine.
     * @param objectId to delete; this is done based on the object id
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void asyncDelete(String objectId, Callback<ObjectModificationResponse> callback) {
        asyncDelete(objectId, callback, CMRequestOptions.NONE);
    }

    /**
     * Delete the given object from CloudMine.
     * @param objectId to delete; this is done based on the object id
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncDelete(String objectId, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) {
        asyncDelete(Collections.singletonList(objectId), callback, options);
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objectIds to delete; this is done based on the object ids
     */
    public void asyncDelete(Collection<String> objectIds) {
        asyncDelete(objectIds, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objectIds to delete; this is done based on the object ids
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void asyncDelete(Collection<String> objectIds, Callback<ObjectModificationResponse> callback) {
        asyncDelete(objectIds, callback, CMRequestOptions.NONE);
    }

    /**
     * Delete the given objects from CloudMine.
     * @param objectIds to delete; this is done based on the object ids
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncDelete(Collection<String> objectIds, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) {
        executeAsyncCommand(createDelete(objectIds, options), callback, objectModificationResponseConstructor());
    }

    /**
     * This will delete ALL the objects associated with this API key. Be careful...
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     */
    public void asyncDeleteAll(Callback<ObjectModificationResponse> callback) {
        executeAsyncCommand(createDeleteAll(), callback, objectModificationResponseConstructor());
    }

    /**
     * This will delete ALL the objects associated with this API key. Be careful...
     */
    public void asyncDeleteAll() {
        asyncDeleteAll(CMCallback.<ObjectModificationResponse>doNothing());
    }

    public void asyncDeleteUser(String userId) {
        asyncDeleteUser(userId, CMCallback.<ObjectModificationResponse>doNothing());
    }

    public void asyncDeleteUser(String userId, Callback<ObjectModificationResponse> callback) {
        executeAsyncCommand(createDeleteUser(userId), callback, objectModificationResponseConstructor());
    }

    /**
     * Delete the CMFile
     * @param file the file to delete
     */
    public void asyncDeleteFile(CMFile file) {
        asyncDeleteFile(file, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the CMFile
     * @param file the file to delete
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link ObjectModificationResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in
     */
    public void asyncDeleteFile(CMFile file, Callback<ObjectModificationResponse> callback) {
        asyncDelete(file.getFileId(), callback);
    }

    /**
     * Delete all the given CMFiles
     * @param files the files to delete
     */
    public void asyncDeleteFiles(Collection<CMFile> files) {
        asyncDeleteFiles(files, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete all the given CMFiles
     * @param files the files to delete
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link ObjectModificationResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in
     */
    public void asyncDeleteFiles(Collection<CMFile> files, Callback<ObjectModificationResponse> callback) {
        Collection<String> keys = new ArrayList<String>(files.size());
        for (CMFile file : files) {
            keys.add(file.getFileId());
        }
        asyncDelete(keys, callback);
    }

    /**
     * Delete the {@link CMFile} with the specified fileId
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     */
    public void asyncDeleteFile(String fileId) {
        asyncDeleteFile(fileId, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Delete the {@link CMFile} with the specified fileId
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link ObjectModificationResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in
     */
    public void asyncDeleteFile(String fileId, Callback<ObjectModificationResponse> callback) {
        asyncDeleteFile(fileId, callback, CMRequestOptions.NONE);
    }

    /**
     * Delete the {@link CMFile} with the specified fileId
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link ObjectModificationResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncDeleteFile(String fileId, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) {
        asyncDelete(fileId, callback, options);
    }

    /**
     * Add the given file to CloudMine
     * @param file the file to add
     */
    public void asyncUpload(CMFile file) {
        asyncUpload(file, CMCallback.<FileCreationResponse>doNothing());
    }

    /**
     * Add the given file to CloudMine
     * @param file the file to add
     * @param callback a {@link Callback} that expects a {@link FileCreationResponse}. It is recommended that you pass in a {@link com.cloudmine.api.rest.callbacks.FileCreationResponseCallback}
     */
    public void asyncUpload(CMFile file, Callback<FileCreationResponse> callback) {
        executeAsyncCommand(createPut(file), callback, fileCreationResponseConstructor());
    }

    /**
     * Retrieve the {@link CMFile} with the specified fileId, if it exists
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     */
    public void asyncLoadFile(String fileId) {
        asyncLoadFile(fileId, CMCallback.<FileLoadResponse>doNothing());
    }

    /**
     * Retrieve the {@link CMFile} with the specified fileId, if it exists
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects a FileLoadResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.FileLoadCallback} is passed in
     */
    public void asyncLoadFile(String fileId, Callback<FileLoadResponse> callback) {
        asyncLoadFile(fileId, callback, CMRequestOptions.NONE);
    }

    /**
     * Retrieve the {@link CMFile} with the specified fileId, if it exists
     * @param fileId the file fileId, either specified when the CMFile was instantiated or returned in the {@link com.cloudmine.api.rest.response.FileCreationResponse} post insertion
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects a FileLoadResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.FileLoadCallback} is passed in
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     */
    public void asyncLoadFile(String fileId, Callback<FileLoadResponse> callback, CMRequestOptions options) {
        executeAsyncCommand(createGetFile(fileId, options), callback, fileLoadResponseResponseConstructor(fileId));
    }

    public void asyncLoadFileMetaData(String fileId, CMRequestOptions options,
            Callback<CMObjectResponse> callback) {
        executeAsyncCommand(createGetFileMetaData(fileId, options), callback, cmObjectResponseConstructor());
    }

    /**
     * Retrieve all the objects
     */
    public void asyncLoadObjects() {
        asyncLoadObjects(CMCallback.<CMObjectResponse>doNothing());
    }

    /**
     * Retrieve all the objects
     * @param callback a Callback that expects a {@link com.cloudmine.api.rest.response.CMObjectResponse}. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used
     */
    public void asyncLoadObjects(Callback<CMObjectResponse> callback) {
        asyncLoadObjects(callback, CMRequestOptions.NONE);
    }

    /**
     * Retrieve all the objects
     * @param callback a Callback that expects a {@link com.cloudmine.api.rest.response.CMObjectResponse}. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used
     * @param options options to apply to the call, such as a server function to pass the results of the call into, paging options, etc
     */
    public void asyncLoadObjects(Callback<CMObjectResponse> callback, CMRequestOptions options) {
        asyncLoadObjects(Collections.<String>emptyList(), callback, options);
    }

    /**
     * Retrieve the object with the given objectId
     * @param objectId the top level objectId of the object to retrieve
     */
    public void asyncLoadObject(String objectId) {
        asyncLoadObject(objectId, CMCallback.<CMObjectResponse>doNothing());
    }

    /**
     * Retrieve the object with the given objectId
     * @param objectId the top level objectId of the object to retrieve
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     */
    public void asyncLoadObject(String objectId, Callback<CMObjectResponse> callback) {
        asyncLoadObjects(Collections.<String>singleton(objectId), callback);
    }

    /**
     * Retrieve the object with the given objectId
     * @param objectId the top level objectId of the object to retrieve
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     * @param options options to apply to the call, such as a server function to pass the results of the call into, paging options, etc
     */
    public void asyncLoadObject(String objectId, Callback<CMObjectResponse> callback, CMRequestOptions options) {
        asyncLoadObjects(Collections.<String>singleton(objectId), callback, options);
    }

    /**
     * Retrieve all the objects with the given objectIds
     * @param objectIds the top level objectIds of the objects to retrieve
     */
    public void asyncLoadObjects(Collection<String> objectIds) {
        asyncLoadObjects(objectIds, CMCallback.<CMObjectResponse>doNothing());
    }

    /**
     * Retrieve all the objects with the given objectIds
     * @param objectIds the top level objectIds of the objects to retrieve
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     */
    public void asyncLoadObjects(Collection<String> objectIds, Callback<CMObjectResponse> callback) {
        asyncLoadObjects(objectIds, callback, CMRequestOptions.NONE);
    }

    /**
     * Retrieve all the objects with the given objectIds
     * @param objectIds the top level objectIds of the objects to retrieve
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     * @param options options to apply to the call, such as a server function to pass the results of the call into, paging options, etc
     */
    public void asyncLoadObjects(Collection<String> objectIds, Callback<CMObjectResponse> callback,
            CMRequestOptions options) {
        executeAsyncCommand(createGetObjects(objectIds, options), callback, cmObjectResponseConstructor());
    }

    /**
     * Retrieve all the objects that match the given search
     * @param searchString the search string to use. For more information on syntax. See <a href="https://cloudmine.me/docs/object-storage#query_syntax">Search query syntax</a>
     */
    public void asyncSearch(String searchString) {
        asyncSearch(searchString, CMCallback.<CMObjectResponse>doNothing());
    }

    /**
     * Retrieve all the objects that match the given search
     * @param searchString the search string to use. For more information on syntax. See <a href="https://cloudmine.me/docs/object-storage#query_syntax">Search query syntax</a>
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     */
    public void asyncSearch(String searchString, Callback<CMObjectResponse> callback) {
        asyncSearch(searchString, callback, CMRequestOptions.NONE);
    }

    /**
     * Retrieve all the objects that match the given search
     * @param searchString the search string to use. For more information on syntax. See <a href="https://cloudmine.me/docs/object-storage#query_syntax">Search query syntax</a>
     * @param callback the callback to pass the results into. It is recommended that {@link com.cloudmine.api.rest.callbacks.CMObjectResponseCallback} is used here
     * @param options options to apply to the call, such as a server function to pass the results of the call into, paging options, etc
     */
    public void asyncSearch(String searchString, Callback<CMObjectResponse> callback, CMRequestOptions options) {
        executeAsyncCommand(createSearch(searchString, options), callback, cmObjectResponseConstructor());
    }

    /**
     * Asynchronously insert the object. If it already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the object to save
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(CMObject toCreate) throws ConversionException {
        asyncInsert(toCreate, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Asynchronously insert the object. If it already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the object to save
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(CMObject toCreate, Callback<ObjectModificationResponse> callback)
            throws ConversionException {
        asyncInsert(toCreate, callback, CMRequestOptions.NONE);
    }

    /**
     * Asynchronously insert the object. If it already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the object to save
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(CMObject toCreate, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) throws ConversionException {
        executeAsyncCommand(createPut(toCreate.transportableRepresentation(), options), callback,
                objectModificationResponseConstructor());
    }

    /**
     * Asynchronously insert all of the objects. If any already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the objects to save
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(Collection<? extends CMObject> toCreate) throws ConversionException {
        asyncInsert(toCreate, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Asynchronously insert all of the objects. If any already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the objects to save
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(Collection<? extends CMObject> toCreate, Callback<ObjectModificationResponse> callback)
            throws ConversionException {
        asyncInsert(toCreate, callback, CMRequestOptions.NONE);
    }

    /**
     * Asynchronously insert all of the objects. If any already exists in CloudMine, its contents will be replaced entirely
     * @param toCreate the objects to save
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @param options options to apply to the call, such as a server function to pass the results of the call into
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncInsert(Collection<? extends CMObject> toCreate, Callback<ObjectModificationResponse> callback,
            CMRequestOptions options) throws ConversionException {
        List<Transportable> transportables = new ArrayList<Transportable>(toCreate.size());
        for (CMObject object : toCreate) {
            transportables.add(new TransportableString(object.asKeyedObject()));
        }
        String jsonStringsCollection = JsonUtilities
                .jsonCollection(transportables.toArray(new Transportable[transportables.size()]))
                .transportableRepresentation();
        executeAsyncCommand(createPut(jsonStringsCollection, options), callback,
                objectModificationResponseConstructor());
    }

    /**
     * Asynchronously update the object. If it already exists in CloudMine, its contents will be merged
     * @param toUpdate the object to update
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncUpdate(CMObject toUpdate) throws ConversionException {
        asyncUpdate(toUpdate, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Asynchronously update the object. If it already exists in CloudMine, its contents will be merged
     * @param toUpdate the object to update
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncUpdate(CMObject toUpdate, Callback<ObjectModificationResponse> callback)
            throws ConversionException {
        executeAsyncCommand(createJsonPost(toUpdate.transportableRepresentation()), callback,
                objectModificationResponseConstructor());
    }

    /**
     * Asynchronously update all of the objects. If any already exists in CloudMine, its contents will be merged
     * @param objects the objects to update
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncUpdate(Collection<? extends CMObject> objects) throws ConversionException {
        asyncUpdate(objects, CMCallback.<ObjectModificationResponse>doNothing());
    }

    /**
     * Asynchronously update all of the objects. If any already exists in CloudMine, its contents will be merged
     * @param objects the objects to update
     * @param callback a Callback that expects an ObjectModificationResponse or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.ObjectModificationResponseCallback} is passed in for this
     * @throws ConversionException if unable to convert to transportable representation; this should not happen unless you are subclassing objects and doing something you shouldn't be with overriding transportableRepresentation This ordinarily should not occur
     */
    public void asyncUpdate(Collection<? extends CMObject> objects, Callback<ObjectModificationResponse> callback)
            throws ConversionException {
        String[] jsonStrings = new String[objects.size()];
        int i = 0;
        for (CMObject cmObject : objects) {
            jsonStrings[i] = cmObject.asKeyedObject();
            i++;
        }
        String json = JsonUtilities.jsonCollection(jsonStrings).transportableRepresentation();
        executeAsyncCommand(createJsonPost(json), callback, objectModificationResponseConstructor());
    }

    /**
     * See {@link #asyncCreateUser(com.cloudmine.api.JavaCMUser, com.cloudmine.api.rest.callbacks.Callback)}
     */
    public void asyncCreateUser(JavaCMUser user) {
        executeAsyncCommand(createPut(user));
    }

    /**
     * Create a new user
     * @param user the user to create
     * @param callback a Callback that expects a {@link CreationResponse}. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CreationResponseCallback} is given here
     */
    public void asyncCreateUser(JavaCMUser user, Callback<CreationResponse> callback) {
        executeAsyncCommand(createPut(user), user.setObjectIdOnCreation(callback), creationResponseConstructor());
    }

    /**
     * Change the user's e-mail address
     * @param oldEmail
     * @param currentPassword
     * @param newEmail
     * @param callback
     */
    public void asyncChangeEmail(String oldEmail, String currentPassword, String newEmail,
            Callback<CMResponse> callback) {
        HttpPost updateEmail = createUpdateEmail(oldEmail, currentPassword, newEmail);
        executeAsyncCommand(updateEmail, callback);
    }

    /**
     * Change the user's name
     * @param oldEmail
     * @param currentPassword
     * @param newEmail
     * @param responseCallback
     */
    public void asyncChangeUserName(String oldEmail, String currentPassword, String newEmail,
            Callback<CMResponse> responseCallback) {
        HttpPost updateUserName = createUpdateUserName(oldEmail, currentPassword, newEmail);
        executeAsyncCommand(updateUserName, responseCallback);
    }

    /**
     * Change the given user's password to newPassword
     * @param user the user whose password is to be changed
     * @param newPassword the new password
     */
    public void asyncChangePassword(JavaCMUser user, String newPassword) {
        asyncChangePassword(user, newPassword, CMCallback.<CMResponse>doNothing());
    }

    /**
     * Change the given user's password to newPassword
     * @param user the user whose password is to be changed
     * @param newPassword the new password
     * @param callback a Callback that expects a CMResponse. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CMResponseCallback} is given here
     */
    public void asyncChangePassword(JavaCMUser user, String newPassword, Callback<CMResponse> callback) {
        asyncChangePassword(user, newPassword, CMRequestOptions.NONE, callback);
    }

    /**
     * Change the given user's password to newPassword
     * @param user the user whose password is to be changed
     * @param newPassword the new password
     * @param callback a Callback that expects a CMResponse. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CMResponseCallback} is given here
     */
    public void asyncChangePassword(JavaCMUser user, String newPassword, CMRequestOptions options,
            Callback<CMResponse> callback) {
        asyncChangePassword(user.getEmail(), user.getUserName(), user.getPassword(), newPassword, options,
                callback);
    }

    public void asyncChangePassword(String email, String oldPassword, String newPassword, CMRequestOptions options,
            Callback<CMResponse> callback) {
        asyncChangePassword(email, null, oldPassword, newPassword, options, callback);
    }

    public void asyncChangePassword(String email, String userName, String oldPassword, String newPassword,
            CMRequestOptions options, Callback<CMResponse> callback) {
        executeAsyncCommand(createChangePassword(email, userName, oldPassword, newPassword, options), callback);
    }

    /**
     * Asynchronously Request that the user with the given e-mail address's password is reset. This will generate a password reset e-mail that will be sent to the user
     * @param email the e-mail address of the user
     */
    public void asyncResetPasswordRequest(String email) {
        asyncResetPasswordRequest(email, CMCallback.<CMResponse>doNothing());
    }

    /**
     * Asynchronously Request that the user with the given e-mail address's password is reset. This will generate a password reset e-mail that will be sent to the user
     * @param email the e-mail address of the user
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link CMResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.CMResponseCallback} is passed in
     */
    public void asyncResetPasswordRequest(String email, Callback<CMResponse> callback) {
        executeAsyncCommand(createResetPassword(email), callback);
    }

    /**
     * Asynchronously confirm that a users password should be reset. Requires the token sent to the user's email address
     * @param token from the e-mail sent to the user
     * @param newPassword the new password
     */
    public void asyncResetPasswordConfirmation(String token, String newPassword) {
        asyncResetPasswordConfirmation(token, newPassword, CMCallback.<CMResponse>doNothing());
    }

    /**
     * Asynchronously confirm that a users password should be reset. Requires the token sent to the user's email address
     * @param token from the e-mail sent to the user
     * @param newPassword the new password
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link CMResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.CMResponseCallback} is passed in
     */
    public void asyncResetPasswordConfirmation(String token, String newPassword, Callback<CMResponse> callback) {
        executeAsyncCommand(createResetPasswordConfirmation(token, newPassword), callback);
    }

    /**
     * Internal method used for completing the social log in process after a redirect
     * @param challenge
     * @param callback
     */
    public void asyncCompleteSocialLogin(String challenge, Callback<CMSocialLoginResponse> callback) {
        HttpGet get = createCompleteSocialGet(challenge);
        executeAsyncCommand(get, callback, CMSocialLoginResponse.CONSTRUCTOR);
    }

    /**
     * Asynchronously log in this user
     * @param user the user to log in
     */
    public void asyncLogin(JavaCMUser user) {
        asyncLogin(user, CMCallback.<LoginResponse>doNothing());
    }

    /**
     * Asynchronously log in this user
     * NOTE: It is recommended that {@link com.cloudmine.api.JavaCMUser#login(com.cloudmine.api.rest.callbacks.Callback)} is used instead of this method,
     * as it will set the user's CMSessionToken properly
     * @param user the user to log in
     * @param callback a {@link com.cloudmine.api.rest.callbacks.Callback} that expects an {@link LoginResponse} or a parent class. It is recommended an {@link com.cloudmine.api.rest.callbacks.LoginResponseCallback} is passed in
     */
    public void asyncLogin(JavaCMUser user, Callback<LoginResponse> callback) {
        if (user.isLoggedIn()) {
            callback.onCompletion(user.createFakeLoginResponse());
        } else {
            executeAsyncCommand(createLoginPost(user), callback, logInResponseConstructor());
        }
    }

    /**
     * Invalidate the given session token. Note that if other session tokens exist for this user, they will still be valid
     * @param token the token to invalidate
     */
    public void asyncLogout(CMSessionToken token) {
        asyncLogout(token, CMCallback.<CMResponse>doNothing());
    }

    /**
     * Invalidate the given session token. Note that if other session tokens exist for this user, they will still be valid
     * @param token the token to invalidate
     * @param callback a {@link Callback} that expects a {@link CMResponse}. It is recommended that a {@link com.cloudmine.api.rest.callbacks.CMResponseCallback} is used here
     */
    public void asyncLogout(CMSessionToken token, Callback<CMResponse> callback) {
        executeAsyncCommand(createLogoutPost(token), callback, cmResponseConstructor());
    }

    /**
     * Registers the given token with CloudMine, so push notifications can be sent to the device. If the UserCMWebService is
     * used, the token will be registered with the specific user.
     * @param senderID The senderID given by Google for Push Notifications
     * @param callback A {@link com.cloudmine.api.rest.callbacks.Callback} that expects a {@link TokenUpdateResponse} class.
     */
    public void registerForGCM(String senderID, Callback<TokenUpdateResponse> callback) {
        HttpPost postRequest = createRegisterGCMPost(senderID);
        executeAsyncCommand(postRequest, callback, tokenUpdateConstructor());
    }

    /**
     * Unregisters the token associated with this device. If it was registered with a specific user, that user's CMWebService should
     * be used to unregister it.
     * @param callback A {@link com.cloudmine.api.rest.callbacks.Callback} that expects a {@link TokenUpdateResponse} class.
     */
    public void unregisterForGCM(Callback<TokenUpdateResponse> callback) {
        HttpDelete deleteRequest = createDeleteToken();
        executeAsyncCommand(deleteRequest, callback, tokenUpdateConstructor());
    }

    /**
     * See {@link #asyncCreateChannel(com.cloudmine.api.CMChannel, com.cloudmine.api.rest.callbacks.Callback)}
     * @param channel
     */
    public void asyncCreateChannel(CMChannel channel) {
        asyncCreateChannel(channel, CMResponseCallback.<PushChannelResponse>doNothing());
    }

    /**
     * Creates a channel and calls into a {@link com.cloudmine.api.rest.callbacks.PushChannelResponseCallback}
     * @param channel
     * @param callback
     */
    public void asyncCreateChannel(CMChannel channel, Callback<PushChannelResponse> callback) {
        HttpPost post = createNotificationChannel(channel);
        executeAsyncCommand(post, callback, PushChannelResponse.CONSTRUCTOR);
    }

    /**
     * See {@link #asyncDeleteChannel(String, com.cloudmine.api.rest.callbacks.Callback)}
     * @param channelName
     */
    public void asyncDeleteChannel(String channelName) {
        asyncDeleteChannel(channelName, CMResponseCallback.<CMResponse>doNothing());
    }

    /**
     * Delete the specified channel then call into the given CMResponseCallback. Will return a success response even
     * if the channel doesn't exist
     * @param channelName
     * @param callback
     */
    public void asyncDeleteChannel(String channelName, Callback<CMResponse> callback) {
        executeAsyncCommand(createDeleteChannel(channelName), callback);
    }

    /**
     * See {@link #asyncSubscribeThisDeviceToChannel(String, com.cloudmine.api.rest.callbacks.Callback)}
     * @param channelName
     */
    public void asyncSubscribeThisDeviceToChannel(String channelName) {
        asyncSubscribeThisDeviceToChannel(channelName, CMResponseCallback.<PushChannelResponse>doNothing());
    }

    /**
     * Subscribe this device to receive pushes on the given channel. Only works on Android devices; is based on the value
     * returned by {@link com.cloudmine.api.DeviceIdentifier#getUniqueId()}
     * @param channelName the channel to subscribe to
     * @param responseCallback a PushChannelResponse callback
     */
    public void asyncSubscribeThisDeviceToChannel(String channelName,
            Callback<PushChannelResponse> responseCallback) {
        HttpPost post = createSubscribeSelf(channelName, true, false);
        executeAsyncCommand(post, responseCallback, PushChannelResponse.CONSTRUCTOR);
    }

    /**
     * See {@link #asyncSubscribeUsersToChannel(String, java.util.Collection, com.cloudmine.api.rest.callbacks.Callback)}
     * @param channelName
     * @param targets
     */
    public void asyncSubscribeUsersToChannel(String channelName,
            Collection<CMPushNotification.UserTarget> targets) {
        asyncSubscribeUsersToChannel(channelName, targets, CMCallback.<PushChannelResponse>doNothing());
    }

    /**
     * Subscribe the specified users to the given channel, and then call into the given PushChannelResponseCallback
     * @param channelName the name of the channel to subscribe the users to
     * @param targets the user's to subscribe
     * @param responseCallback
     */
    public void asyncSubscribeUsersToChannel(String channelName, Collection<CMPushNotification.UserTarget> targets,
            Callback<PushChannelResponse> responseCallback) {
        HttpPost post = createSubscribeUsers(channelName, targets);
        executeAsyncCommand(post, responseCallback, PushChannelResponse.CONSTRUCTOR);
    }

    public void asyncUnsubscribeUsersFromChannel(String channelName, Collection<String> userIds,
            Callback<PushChannelResponse> responseCallback) {
        HttpDelete delete = createUnsubscribeUsers(channelName, userIds);
        executeAsyncCommand(delete, responseCallback, PushChannelResponse.CONSTRUCTOR);
    }

    /**
     * Get the channel names that the user identified by the given id is subscribed to. Channel names come back as a
     * list of strings
     * @param userId user object id
     * @param callback a ListOfValuesResponseCallback of Strings
     */
    public void asyncLoadSubscribedChannelsForUser(String userId, Callback<ListOfValuesResponse<String>> callback) {
        HttpGet get = createListChannels(userId);
        executeAsyncCommand(get, callback, ListOfValuesResponse.CONSTRUCTOR());
    }

    /**
     * Get the channel names that the device identified by the given id is subscribed to. Channel names come back as a
     * list of strings. Device id can be obtained through {@link com.cloudmine.api.DeviceIdentifier#getUniqueId()}
     * @param deviceId
     * @param callback a ListOfValuesResponseCallback of Strings
     */
    public void asyncLoadSubscribedChannelsForDevice(String deviceId,
            Callback<ListOfValuesResponse<String>> callback) {
        HttpGet get = createListChannelsForDevice(deviceId);
        executeAsyncCommand(get, callback, ListOfValuesResponse.CONSTRUCTOR());
    }

    /**
     * See {@link #asyncSendNotification(com.cloudmine.api.CMPushNotification, com.cloudmine.api.rest.callbacks.Callback)}
     * @param notification
     */
    public void asyncSendNotification(CMPushNotification notification) {
        asyncSendNotification(notification, CMResponseCallback.<CMResponse>doNothing());
    }

    /**
     * Send a notification then call into the given CMResponse. A success response has no body
     * @param notification
     * @param callback
     */
    public void asyncSendNotification(CMPushNotification notification, Callback<CMResponse> callback) {
        HttpPost postRequest = createNotificationPost(notification);
        executeAsyncCommand(postRequest, callback);
    }

    /**
     * Make a blocking call to load the object associated with the given objectId
     * @param objectId of the object to load
     * @return a CMObjectResponse containing success or failure, and the loaded object if it exists and the call was a success
     * @throws NetworkException if unable to perform the request
     *
     */
    public CMObjectResponse loadObject(String objectId) throws NetworkException {
        return loadObjects(Collections.singletonList(objectId));
    }

    /**
     * Make a blocking call to load all of the objects associated with the given objectIds
     * @param objectIds of the objects to load
     * @return a CMObjectResponse containing success or failure, and the loaded objects if they exist and the call was a success
     * @throws NetworkException if unable to perform the request
     */
    public CMObjectResponse loadObjects(Collection<String> objectIds) throws NetworkException {
        return executeCommand(createGetObjects(objectIds), cmObjectResponseConstructor());
    }

    /**
     * Make a blocking call to load all of the objects
     * @return a CMObjectResponse containing success or failure, and the loaded objects if they exist and the call was a success
     * @throws NetworkException if unable to perform the request
     */
    public CMObjectResponse loadAllObjects() throws NetworkException {
        return executeCommand(createGet(), cmObjectResponseConstructor());
    }

    /**
     * Make a blocking call to load the specified file
     * @param fileId the name of the file to load
     * @return a FileLoadResponse that contains the loaded file, if the call was a success
     * @throws NetworkException if unable to perform the network call
     * @throws CreationException if unable to create the CMFile
     */
    public FileLoadResponse loadFile(String fileId) throws NetworkException, CreationException {
        try {
            HttpResponse response = httpClient.execute(createGetFile(fileId));
            return new FileLoadResponse(response, fileId);
        } catch (IOException e) {
            LOG.error("IOException getting file", e);
            throw new CreationException("Couldn't get file because of IOException", e);
        }
    }

    /**
     * Make a blocking call to search for CloudMine objects.
     * @param searchString the search string to use. For more information on syntax. See <a href="https://cloudmine.me/docs/object-storage#query_syntax">Search query syntax</a>
     * @return  the {@link com.cloudmine.api.rest.response.CMObjectResponse} containing the retrieved objects.
     * @throws NetworkException if unable to perform the network call
     */
    public CMObjectResponse loadSearch(String searchString) throws NetworkException {
        HttpGet get = createSearch(searchString);
        return executeCommand(get, cmObjectResponseConstructor());
    }

    /**
     * Make a blocking call to directly insert a transportable representation into CloudMine
     * @param transport a valid transportable representation of a CloudMine object
     * @return the ObjectModificationResponse containing success and error values
     * @throws NetworkException if unable to perform the network call
     */
    public ObjectModificationResponse insert(String transport) throws NetworkException {
        HttpPut put = createPut(transport);
        return executeCommand(put, objectModificationResponseConstructor());
    }

    /**
     * Make a blocking call to directly update an object using a transportable representation in CloudMine. If the object already exists, its values will be updated; otherwise it will be inserted
     * @param transport a valid transportable representation of a CloudMine object
     * @return the ObjectModificationResponse containing success and error values
     * @throws NetworkException if unable to perform the network call
     */
    public ObjectModificationResponse update(String transport) throws NetworkException {
        HttpPost post = createJsonPost(transport);
        return executeCommand(post, objectModificationResponseConstructor());
    }

    /**
     * Make a blocking call to insert a file
     * @param file to insert
     * @return the FileCreationResponse that indicates success and failure, and has the file name
     * @throws NetworkException if unable to perform the network call
     */
    public FileCreationResponse insert(CMFile file) throws NetworkException {
        return executeCommand(createPut(file), fileCreationResponseConstructor());
    }

    /**
     * Change the given user's password to newPassword
     * @param user the user whose password is to be changed
     * @param newPassword the new password
     * @return a CMResponse that indicates success or failure
     * @throws NetworkException if unable to perform the network call
     */
    public CMResponse changePassword(JavaCMUser user, String newPassword) throws NetworkException {
        return executeCommand(createChangePassword(user, newPassword, CMRequestOptions.NONE));
    }

    /**
     * Make a blocking call to create this user
     * @return  the {@link LoginResponse} which will include the CMSessionToken that authenticates this user and provides access to the user level store
     * @throws NetworkException if unable to perform the network call
     * @throws ConversionException if unable to convert this user to a transportable representation. This should never happen unless you have subclassed CMUser and overridden transportableRepresentation
     */
    public CreationResponse insert(JavaCMUser user) throws NetworkException, ConversionException {
        return executeCommand(createPut(user), CreationResponse.CONSTRUCTOR);
    }

    /**
     * Make a blocking call to log in this user
     * @param user to log in
     * @return a LoginResponse that will contain the CMSessionToken used to validate user requests
     * @throws NetworkException if unable to perform the network call
     */
    public LoginResponse login(JavaCMUser user) throws NetworkException {
        if (user.isLoggedIn()) {
            return user.createFakeLoginResponse();
        }
        return executeCommand(createLoginPost(user), logInResponseConstructor());
    }

    /**
     * Make a blocking call to invalidate this sessionToken
     * @param sessionToken to invalidate
     * @return a CMResponse that indicates success or failure
     * @throws NetworkException if unable to perform the network call
     */
    public CMResponse logout(CMSessionToken sessionToken) throws NetworkException {
        return executeCommand(createLogoutPost(sessionToken));
    }

    private void executeAsyncCommand(HttpUriRequest message) {
        executeAsyncCommand(message, CMCallback.doNothing(), cmResponseConstructor());
    }

    void executeAsyncCommand(HttpUriRequest message, Callback callback) {
        executeAsyncCommand(message, callback, cmResponseConstructor());
    }

    <T> void executeAsyncCommand(HttpUriRequest message, final Callback callback,
            ResponseConstructor<T> constructor) {
        final long startTime = System.currentTimeMillis();
        callback.setStartTime(startTime);
        asyncHttpClient.executeCommand(message, callback, constructor);
    }

    private CMResponse executeCommand(HttpUriRequest message) throws NetworkException {
        return executeCommand(message, cmResponseConstructor());
    }

    private <T extends ResponseBase> T executeCommand(HttpUriRequest message, ResponseConstructor<T> constructor)
            throws NetworkException {
        HttpResponse response = null;
        try {
            response = httpClient.execute(message);
            return constructor.construct(response);
        } catch (IOException e) {
            LOG.error("Error executing command: " + message.getURI(), e);
            throw new NetworkException("Couldn't execute command, IOException: ", e);
        } finally {
            CMWebService.consumeEntityResponse(response);
        }
    }

    //**************************Http commands*****************************************

    private HttpGet createCompleteSocialGet(String challenge) {
        String url = baseUrl.copy().account().social().login().status().statusChallenge(challenge).asUrlString();
        HttpGet get = new HttpGet(url);
        addCloudMineHeader(get);
        return get;
    }

    private HttpGet createSearch(String search) {
        return createSearch(search, CMRequestOptions.NONE);
    }

    private HttpGet createSearch(String search, CMRequestOptions options) {
        HttpGet get = new HttpGet(baseUrl.copy().search(search).options(options).asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    private HttpDelete createDeleteUser(String userId) {
        HttpDelete delete = new HttpDelete(baseUrl.copy().account().addKey(userId).asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpDelete createDeleteAll() {
        HttpDelete delete = new HttpDelete(baseUrl.copy().deleteAll().asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpDelete createDelete(String key) {
        return createDelete(key, CMRequestOptions.NONE);
    }

    private HttpDelete createDelete(String key, CMRequestOptions options) {
        HttpDelete delete = new HttpDelete(baseUrl.copy().delete(key).options(options).asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpDelete createDelete(Collection<String> keys) {
        return createDelete(keys, CMRequestOptions.NONE);
    }

    private HttpDelete createDeleteChannel(String channelName) {
        HttpDelete delete = new HttpDelete(baseUrl.copy().push().channel().addAction(channelName).asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpDelete createDelete(Collection<String> keys, CMRequestOptions options) {
        HttpDelete delete = new HttpDelete(baseUrl.copy().delete(keys).options(options).asUrlString());

        addCloudMineHeader(delete);
        return delete;
    }

    private HttpDelete createDeleteToken() {
        HttpDelete delete = new HttpDelete(baseUrl.copy().device().asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpPut createPut(String json) {
        return createPut(json, CMRequestOptions.NONE);
    }

    private HttpPut createPut(String json, CMRequestOptions options) {
        HttpPut put = new HttpPut(baseUrl.copy().text().options(options).asUrlString());
        addCloudMineHeader(put);
        addJson(put, json);
        return put;
    }

    private HttpPut createPut(JavaCMUser user) throws ConversionException {
        HttpPut put = new HttpPut(baseUrl.copy().account().create().asUrlString());
        addCloudMineHeader(put);
        addJson(put, user.transportableRepresentation());
        return put;
    }

    private HttpPut createPut(CMFile file) {
        HttpPut put = new HttpPut(baseUrl.copy().binary(file.getFileId()).asUrlString());
        addCloudMineHeader(put);
        put.setEntity(new ByteArrayEntity(file.getFileContents()));
        put.addHeader("Content-Type", file.getMimeType());
        return put;
    }

    private HttpPost createNotificationPost(CMPushNotification pushNotification) {
        HttpPost post = createPost(baseUrl.copy().push().asUrlString());
        addJson(post, pushNotification.transportableRepresentation());
        return post;
    }

    private HttpPost createNotificationChannel(CMChannel channel) {
        HttpPost post = createPost(baseUrl.copy().push().channel().asUrlString());
        addJson(post, channel);
        return post;
    }

    HttpGet createListChannels(String userid) {
        CMURLBuilder partialUrl = baseUrl.copy().account().channels();
        if (Strings.isNotEmpty(userid))
            partialUrl.addQuery("userid", userid);
        HttpGet get = new HttpGet(partialUrl.asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    HttpGet createListChannelsForDevice(String deviceId) {
        HttpGet get = new HttpGet(baseUrl.copy().device().addAction(deviceId).channels().asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    HttpPost createSubscribeSelf(String channel, boolean isDevice, boolean isUser) {
        HttpPost post = createPost(
                baseUrl.copy().notUser().push().channel().addAction(channel).subscribe().asUrlString());
        addJson(post, JsonUtilities.wrap(JsonUtilities.createJsonProperty("user", isUser) + ", "
                + JsonUtilities.createJsonProperty("device", isDevice)));
        return post;
    }

    private static final String IS_USER_JSON = JsonUtilities.wrap(JsonUtilities.createJsonProperty("user", true));

    HttpPost createUnsubscribeSelf(String channel) {
        HttpPost post = createPost(
                baseUrl.copy().notUser().push().channel().addAction(channel).unsubscribe().asUrlString());
        addJson(post, IS_USER_JSON);
        return post;
    }

    private HttpDelete createUnsubscribeUsers(String channelName, Collection<String> objectIds) {
        HttpDelete delete = new HttpDelete(
                baseUrl.copy().push().channel().addAction(channelName).userIds().ids(objectIds).asUrlString());
        addCloudMineHeader(delete);
        return delete;
    }

    private HttpPost createSubscribeUsers(String channel, Collection<CMPushNotification.UserTarget> targets) {
        HttpPost post = createPost(baseUrl.copy().push().channel().addAction(channel).users().asUrlString());
        String json = JsonUtilities.objectToJson(targets);
        addJson(post, json);
        return post;
    }

    private HttpPost createJsonPost(String json) {
        HttpPost post = createPost(baseUrl.copy().text().asUrlString());
        addJson(post, json);
        return post;
    }

    private HttpPost createLoginPost(JavaCMUser user) {
        HttpPost post = createPost(baseUrl.copy().account().login().asUrlString());
        addAuthorizationHeader(user, post);
        return post;
    }

    private HttpPost createLogoutPost(CMSessionToken sessionToken) {
        HttpPost post = createPost(baseUrl.copy().account().logout().asUrlString());
        post.addHeader("X-CloudMine-SessionToken", sessionToken.getSessionToken());
        return post;
    }

    private HttpPost createUpdateEmail(String oldEmail, String currentPassword, String newEmail) {
        HttpPost post = createPost(baseUrl.copy().account().credentials().asUrlString());
        addAuthorizationHeader(oldEmail, null, currentPassword, post);
        addJson(post, JsonUtilities.wrap(JsonUtilities.createJsonProperty(JavaCMUser.EMAIL_KEY, newEmail)));
        return post;
    }

    private HttpPost createUpdateUserName(String oldUserName, String currentPassword, String newUserName) {
        HttpPost post = createPost(baseUrl.copy().account().credentials().asUrlString());
        addAuthorizationHeader(null, oldUserName, currentPassword, post);
        addJson(post, JsonUtilities.wrap(JsonUtilities.createJsonProperty(JavaCMUser.USERNAME_KEY, newUserName)));
        return post;
    }

    private HttpPost createRegisterGCMPost(String senderID) {
        HttpPost post = createPost(baseUrl.copy().device().asUrlString());
        Map<String, String> body = new HashMap<String, String>();
        body.put("registration_id", senderID);
        addJson(post, JsonUtilities.mapToJson(body));
        return post;
    }

    private HttpPost createPost(String url) {
        HttpPost post = new HttpPost(url);
        addCloudMineHeader(post);
        return post;
    }

    HttpGet createGet() {
        HttpGet get = new HttpGet(baseUrl.copy().text().asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    HttpGet createGet(String url) {
        HttpGet get = new HttpGet(url);
        addCloudMineHeader(get);
        return get;
    }

    private HttpGet createGetFile(String key) {
        return createGetFile(key, CMRequestOptions.NONE);
    }

    private HttpGet createGetFile(String key, CMRequestOptions options) {
        HttpGet get = new HttpGet(baseUrl.copy().binary(key).options(options).asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    private HttpGet createGetFileMetaData(String fileId, CMRequestOptions options) {
        HttpGet get = new HttpGet(
                baseUrl.copy().search(createFileMetaDataSearch(fileId)).options(options).asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    private String createFileMetaDataSearch(String fileId) {
        return new StringBuilder("[").append(JsonUtilities.TYPE_KEY).append(" = \"").append(CMFile.TYPE_VALUE)
                .append("\", ").append(JsonUtilities.OBJECT_ID_KEY).append(" = \"").append(fileId).append("\"]")
                .toString();
    }

    private HttpGet createGetAllUsers() {
        HttpGet get = new HttpGet(baseUrl.copy().account().asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    private HttpGet createProfileSearch(String searchString) {
        return createProfileSearch(searchString, CMRequestOptions.NONE);
    }

    private HttpGet createProfileSearch(String searchString, CMRequestOptions options) {
        return createGet(baseUrl.copy().account().search(searchString, "p").options(options).asUrlString());
    }

    private HttpGet createGetObjects(Collection<String> keys) {
        return createGetObjects(keys, CMRequestOptions.NONE);
    }

    private HttpGet createGetObjects(Collection<String> keys, CMRequestOptions options) {
        HttpGet get = new HttpGet(baseUrl.copy().text().objectIds(keys).options(options).asUrlString());
        addCloudMineHeader(get);
        return get;
    }

    private HttpPost createResetPasswordConfirmation(String token, String newPassword) {
        HttpPost post = new HttpPost(baseUrl.copy().account().password().reset().addAction(token).asUrlString());
        addCloudMineHeader(post);
        try {
            addJson(post,
                    JsonUtilities.jsonCollection(JsonUtilities.createJsonProperty(PASSWORD_KEY, newPassword)));
        } catch (ConversionException e) {
            LOG.error("Unable to create jsoncollection", e); //this should not happen ever so we swallow
        }
        return post;
    }

    private HttpPost createResetPassword(String email) {
        HttpPost post = new HttpPost(baseUrl.copy().account().password().reset().asUrlString());
        addCloudMineHeader(post);
        try {
            addJson(post, JsonUtilities.jsonCollection(JsonUtilities.createJsonProperty(EMAIL_KEY, email)));
        } catch (ConversionException e) {
            LOG.error("Unable to create json collection from email key", e); //this should never happen
        }
        return post;
    }

    private HttpPost createChangePassword(JavaCMUser user, String newPassword, CMRequestOptions options) {
        return createChangePassword(user.getEmail(), user.getUserName(), user.getPassword(), newPassword, options);
    }

    private HttpPost createChangePassword(String email, String userName, String oldPassword, String newPassword,
            CMRequestOptions options) {
        HttpPost post = new HttpPost(baseUrl.copy().account().password().change().options(options).asUrlString());
        addCloudMineHeader(post);
        addAuthorizationHeader(email, userName, oldPassword, post);
        try {
            addJson(post,
                    JsonUtilities.jsonCollection(JsonUtilities.createJsonProperty(PASSWORD_KEY, newPassword)));
        } catch (ConversionException e) {
            LOG.error("Unable to create json collection from property", e); //this should never happen
        }
        return post;
    }

    protected void addJson(HttpEntityEnclosingRequestBase message, String json) {
        if (json == null)
            json = JsonUtilities.EMPTY_JSON;
        if (!message.containsHeader(JSON_HEADER.getName())) {
            message.addHeader(JSON_HEADER);
        }
        try {
            message.setEntity(new StringEntity(json, JSON_ENCODING));
        } catch (UnsupportedEncodingException e) {
            LOG.error("Error encoding json", e);
        }
    }

    private void addJson(HttpEntityEnclosingRequestBase message, Transportable transportable)
            throws ConversionException {
        addJson(message, transportable.transportableRepresentation());
    }

    protected void addAuthorizationHeader(JavaCMUser user, HttpEntityEnclosingRequestBase post) {
        addAuthorizationHeader(user.getEmail(), user.getUserName(), user.getPassword(), post);
    }

    protected void addAuthorizationHeader(String email, String userName, String password,
            HttpEntityEnclosingRequestBase post) {
        String authName = Strings.isNotEmpty(email) ? email : userName;
        post.addHeader(AUTHORIZATION_KEY, "Basic " + JavaCMUser.encode(authName, password));
    }

    protected void addCloudMineHeader(AbstractHttpMessage message) {
        for (Header header : LibrarySpecificClassCreator.getCreator().getHeaderFactory()
                .getCloudMineHeaders(apiKey)) {
            message.addHeader(header);
        }
    }

    //**********************RESPONSE CONSTRUCTORS******************************
    protected ResponseConstructor<FileCreationResponse> fileCreationResponseConstructor() {
        return FileCreationResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<ObjectModificationResponse> objectModificationResponseConstructor() {
        return ObjectModificationResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<CreationResponse> creationResponseConstructor() {
        return CreationResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<CMResponse> cmResponseConstructor() {
        return CMResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<FileLoadResponse> fileLoadResponseResponseConstructor(String key) {
        return FileLoadResponse.constructor(key);
    }

    protected ResponseConstructor<LoginResponse> logInResponseConstructor() {
        return LoginResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<CMObjectResponse> cmObjectResponseConstructor() {
        return CMObjectResponse.CONSTRUCTOR;
    }

    protected ResponseConstructor<TokenUpdateResponse> tokenUpdateConstructor() {
        return TokenUpdateResponse.CONSTRUCTOR;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        CMWebService that = (CMWebService) o;

        if (!asyncHttpClient.equals(that.asyncHttpClient))
            return false;
        if (!baseUrl.copy().equals(that.baseUrl))
            return false;
        if (!httpClient.equals(that.httpClient))
            return false;
        if (loggedInSessionToken != null ? !loggedInSessionToken.equals(that.loggedInSessionToken)
                : that.loggedInSessionToken != null)
            return false;
        if (loggedInUserServices != null ? !loggedInUserServices.equals(that.loggedInUserServices)
                : that.loggedInUserServices != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = baseUrl.copy().hashCode();
        result = 31 * result + httpClient.hashCode();
        result = 31 * result + asyncHttpClient.hashCode();
        result = 31 * result + (loggedInSessionToken != null ? loggedInSessionToken.hashCode() : 0);
        result = 31 * result + (loggedInUserServices != null ? loggedInUserServices.hashCode() : 0);
        return result;
    }
}