com.microsoft.windowsazure.mobileservices.notifications.MobileServicePush.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.windowsazure.mobileservices.notifications.MobileServicePush.java

Source

/*
Copyright (c) Microsoft Open Technologies, Inc.
All Rights Reserved
Apache 2.0 License
     
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     
 http://www.apache.org/licenses/LICENSE-2.0
     
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
     
See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */

/**
 * MobileServicePush.java
 */
package com.microsoft.windowsazure.mobileservices.notifications;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import android.util.Pair;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.microsoft.windowsazure.mobileservices.MobileServiceClient;
import com.microsoft.windowsazure.mobileservices.MobileServiceException;
import com.microsoft.windowsazure.mobileservices.http.MobileServiceConnection;
import com.microsoft.windowsazure.mobileservices.http.MobileServiceHttpClient;
import com.microsoft.windowsazure.mobileservices.http.ServiceFilterResponse;

import org.apache.http.Header;
import org.apache.http.protocol.HTTP;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * The notification hub client
 */
public class MobileServicePush {

    /**
     * Prefix for Storage keys
     */
    private static final String STORAGE_PREFIX = "__NH_";

    /**
     * Prefix for registration information keys in local storage
     */
    private static final String REGISTRATION_NAME_STORAGE_KEY = "REG_NAME_";

    /**
     * Storage Version key
     */
    private static final String STORAGE_VERSION_KEY = "STORAGE_VERSION";

    /**
     * Storage Version
     */
    private static final String STORAGE_VERSION = "1.0.0";

    /**
     * PNS Handle Key
     */
    private static final String PNS_HANDLE_KEY = "PNS_HANDLE";

    /**
     * New registration location header name
     */
    private static final String NEW_REGISTRATION_LOCATION_HEADER = "Location";

    /**
     * Push registration path
     */
    private static final String PNS_API_URL = "push";

    /**
     * The class used to make HTTP clients associated with this instance
     */
    private MobileServiceHttpClient mHttpClient;

    /**
     * SharedPreferences reference used to access local storage
     */
    private SharedPreferences mSharedPreferences;

    /**
     * Factory which creates Registrations according the PNS supported on device
     */
    private PnsSpecificRegistrationFactory mPnsSpecificRegistrationFactory;

    /**
     * Flag to signal the need of refreshing local storage
     */
    private boolean mIsRefreshNeeded = false;

    /**
     * Creates a new NotificationHub client
     *
     * @param notificationHubPath Notification Hub path
     * @param connectionString    Notification Hub connection string
     * @param context             Android context used to access SharedPreferences
     */
    public MobileServicePush(MobileServiceClient client, Context context) {
        mPnsSpecificRegistrationFactory = new PnsSpecificRegistrationFactory();

        mHttpClient = new MobileServiceHttpClient(client);

        if (context == null) {
            throw new IllegalArgumentException("context");
        }

        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());

        verifyStorageVersion();
    }

    private static boolean isNullOrWhiteSpace(String str) {
        return str == null || str.trim().equals("");
    }

    /**
     * Registers the client for native notifications with the specified tags
     *
     * @param pnsHandle PNS specific identifier
     * @param tags      Tags to use in the registration
     * @return Future with Registration Information
     */
    public ListenableFuture<Registration> register(String pnsHandle, String[] tags) {

        final SettableFuture<Registration> resultFuture = SettableFuture.create();

        if (isNullOrWhiteSpace(pnsHandle)) {
            resultFuture.setException(new IllegalArgumentException("pnsHandle"));
            return resultFuture;
        }

        final Registration registration = mPnsSpecificRegistrationFactory.createNativeRegistration();
        registration.setPNSHandle(pnsHandle);
        registration.addTags(tags);

        ListenableFuture<String> registerInternalFuture = registerInternal(registration);

        Futures.addCallback(registerInternalFuture, new FutureCallback<String>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(String v) {
                resultFuture.set(registration);
            }
        });

        return resultFuture;
    }

    /**
     * Registers the client for native notifications with the specified tags
     *
     * @param pnsHandle PNS specific identifier
     * @param callback  The callback to invoke after the Push execution
     * @param tags      Tags to use in the registration
     * @deprecated use {@link register(String pnsHandle, String[] tags)} instead
     */
    public void register(String pnsHandle, String[] tags, final RegistrationCallback callback) {
        ListenableFuture<Registration> registerFuture = register(pnsHandle, tags);

        Futures.addCallback(registerFuture, new FutureCallback<Registration>() {
            @Override
            public void onFailure(Throwable exception) {
                if (exception instanceof Exception) {
                    callback.onRegister(null, (Exception) exception);
                }
            }

            @Override
            public void onSuccess(Registration registration) {
                callback.onRegister(registration, null);
            }
        });
    }

    /**
     * Registers the client for template notifications with the specified tags
     *
     * @param pnsHandle    PNS specific identifier
     * @param templateName The template name
     * @param template     The template body
     * @param tags         The tags to use in the registration
     * @return Future with TemplateRegistration Information
     */
    public ListenableFuture<TemplateRegistration> registerTemplate(String pnsHandle, String templateName,
            String template, String[] tags) {

        final SettableFuture<TemplateRegistration> resultFuture = SettableFuture.create();

        if (isNullOrWhiteSpace(pnsHandle)) {
            resultFuture.setException(new IllegalArgumentException("pnsHandle"));
            return resultFuture;
        }

        if (isNullOrWhiteSpace(templateName)) {
            resultFuture.setException(new IllegalArgumentException("templateName"));
            return resultFuture;
        }

        if (isNullOrWhiteSpace(template)) {
            resultFuture.setException(new IllegalArgumentException("template"));
            return resultFuture;
        }

        final TemplateRegistration registration = mPnsSpecificRegistrationFactory.createTemplateRegistration();
        registration.setPNSHandle(pnsHandle);
        registration.setName(templateName);
        registration.setTemplateBody(template);
        registration.addTags(tags);

        ListenableFuture<String> registerInternalFuture = registerInternal(registration);

        Futures.addCallback(registerInternalFuture, new FutureCallback<String>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(String v) {
                resultFuture.set(registration);
            }
        });

        return resultFuture;
    }

    /**
     * Registers the client for template notifications with the specified tags
     *
     * @param pnsHandle    PNS specific identifier
     * @param templateName The template name
     * @param template     The template body
     * @param tags         The tags to use in the registration
     * @param callback     The operation callback
     * @deprecated use {@link registerTemplate(String pnsHandle, String
     * templateName, String template, String[] tags)} instead
     */
    public void registerTemplate(String pnsHandle, String templateName, String template, String[] tags,
            final TemplateRegistrationCallback callback) {
        ListenableFuture<TemplateRegistration> registerFuture = registerTemplate(pnsHandle, templateName, template,
                tags);

        Futures.addCallback(registerFuture, new FutureCallback<TemplateRegistration>() {
            @Override
            public void onFailure(Throwable exception) {
                if (exception instanceof Exception) {
                    callback.onRegister(null, (Exception) exception);
                }
            }

            @Override
            public void onSuccess(TemplateRegistration registration) {
                callback.onRegister(registration, null);
            }
        });
    }

    /**
     * Unregisters the client for native notifications
     *
     * @return Future with TemplateRegistration Information
     */
    public ListenableFuture<Void> unregister() {
        return unregisterInternal(Registration.DEFAULT_REGISTRATION_NAME);
    }

    /**
     * Unregisters the client for native notifications
     *
     * @param callback The operation callback
     * @deprecated use {@link unregister()} instead
     */
    public void unregister(final UnregisterCallback callback) {
        ListenableFuture<Void> unregisterFuture = unregister();

        Futures.addCallback(unregisterFuture, new FutureCallback<Void>() {
            @Override
            public void onFailure(Throwable exception) {
                if (exception instanceof Exception) {
                    callback.onUnregister((Exception) exception);
                }
            }

            @Override
            public void onSuccess(Void v) {
                callback.onUnregister(null);
            }
        });
    }

    /**
     * Unregisters the client for template notifications of a specific template
     *
     * @param templateName The template name
     * @return Future with TemplateRegistration Information
     */
    public ListenableFuture<Void> unregisterTemplate(String templateName) {
        if (isNullOrWhiteSpace(templateName)) {
            throw new IllegalArgumentException("templateName");
        }

        return unregisterInternal(templateName);
    }

    /**
     * Unregisters the client for template notifications of a specific template
     *
     * @param templateName The template name
     * @param callback     The operation callback
     * @deprecated use {@link unregisterTemplate(String templateName)} instead
     */
    public void unregisterTemplate(String templateName, final UnregisterCallback callback) {
        ListenableFuture<Void> unregisterTemplateFuture = unregisterTemplate(templateName);

        Futures.addCallback(unregisterTemplateFuture, new FutureCallback<Void>() {
            @Override
            public void onFailure(Throwable exception) {
                if (exception instanceof Exception) {
                    callback.onUnregister((Exception) exception);
                }
            }

            @Override
            public void onSuccess(Void v) {
                callback.onUnregister(null);
            }
        });
    }

    /**
     * Unregisters the client for all notifications
     *
     * @param pnsHandle PNS specific identifier
     * @return Future with TemplateRegistration Information
     */
    public ListenableFuture<Void> unregisterAll(String pnsHandle) {

        final SettableFuture<Void> resultFuture = SettableFuture.create();

        ListenableFuture<ArrayList<Registration>> fullRegistrationInformationFuture = getFullRegistrationInformation(
                pnsHandle);

        Futures.addCallback(fullRegistrationInformationFuture, new FutureCallback<ArrayList<Registration>>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ArrayList<Registration> registrations) {

                ListenableFuture<Void> unregisterAllInternalFuture = unregisterAllInternal(registrations);

                Futures.addCallback(unregisterAllInternalFuture, new FutureCallback<Void>() {
                    @Override
                    public void onFailure(Throwable exception) {
                        resultFuture.setException(exception);
                    }

                    @Override
                    public void onSuccess(Void v) {
                        resultFuture.set(null);
                    }
                });
            }
        });

        return resultFuture;
    }

    /**
     * Unregisters the client for all notifications
     *
     * @param pnsHandle PNS specific identifier
     * @param callback  The operation callback
     * @deprecated use {@link unregisterAll(String pnsHandle)} instead
     */
    public void unregisterAll(String pnsHandle, final UnregisterCallback callback) {
        ListenableFuture<Void> unregisterAllFuture = unregisterAll(pnsHandle);

        Futures.addCallback(unregisterAllFuture, new FutureCallback<Void>() {
            @Override
            public void onFailure(Throwable exception) {
                if (exception instanceof Exception) {
                    callback.onUnregister((Exception) exception);
                }
            }

            @Override
            public void onSuccess(Void v) {
                callback.onUnregister(null);
            }
        });
    }

    private ListenableFuture<Void> unregisterAllInternal(ArrayList<Registration> registrations) {
        final SettableFuture<Void> resultFuture = SettableFuture.create();

        final SyncState state = new SyncState();

        state.size = registrations.size();

        final CopyOnWriteArrayList<String> concurrentArray = new CopyOnWriteArrayList<String>();

        final Object syncObject = new Object();

        if (state.size == 0) {

            removeAllRegistrationsId();

            mIsRefreshNeeded = false;

            resultFuture.set(null);

            return resultFuture;
        }

        for (final Registration registration : registrations) {

            ListenableFuture<Void> serviceFilterFuture = deleteRegistrationInternal(registration.getName(),
                    registration.getRegistrationId());

            Futures.addCallback(serviceFilterFuture, new FutureCallback<Void>() {
                @Override
                public void onFailure(Throwable exception) {

                    if (exception != null) {
                        synchronized (syncObject) {
                            if (!state.alreadyReturn) {
                                state.alreadyReturn = true;

                                resultFuture.setException(exception);

                                return;
                            }
                        }
                    }
                }

                @Override
                public void onSuccess(Void v) {
                    concurrentArray.add(registration.getRegistrationId());

                    if (concurrentArray.size() == state.size && !state.alreadyReturn) {
                        removeAllRegistrationsId();

                        mIsRefreshNeeded = false;

                        resultFuture.set(null);

                        return;
                    }
                }
            });
        }

        return resultFuture;
    }

    private ListenableFuture<Void> refreshRegistrationInformationIfNeeded(String pnsHandle) {

        final SettableFuture<Void> resultFuture = SettableFuture.create();

        if (!mIsRefreshNeeded) {
            resultFuture.set(null);

            return resultFuture;
        }

        return refreshRegistrationInformation(pnsHandle);
    }

    private ListenableFuture<Void> refreshRegistrationInformation(String pnsHandle) {

        final SettableFuture<Void> resultFuture = SettableFuture.create();

        if (isNullOrWhiteSpace(pnsHandle)) {
            resultFuture.setException(new IllegalArgumentException("pnsHandle"));
            return resultFuture;
        }

        // delete old registration information
        Editor editor = mSharedPreferences.edit();
        Set<String> keys = mSharedPreferences.getAll().keySet();
        for (String key : keys) {
            if (key.startsWith(STORAGE_PREFIX + REGISTRATION_NAME_STORAGE_KEY)) {
                editor.remove(key);
            }
        }

        editor.commit();

        ListenableFuture<ArrayList<Registration>> getFullRegistrationInformationFuture = getFullRegistrationInformation(
                pnsHandle);

        Futures.addCallback(getFullRegistrationInformationFuture, new FutureCallback<ArrayList<Registration>>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ArrayList<Registration> registrations) {
                for (Registration registration : registrations) {

                    try {
                        storeRegistrationId(registration.getName(), registration.getRegistrationId(),
                                registration.getPNSHandle());
                    } catch (Exception e) {
                        resultFuture.setException(e);

                        return;
                    }
                }

                mIsRefreshNeeded = false;

                resultFuture.set(null);
            }
        });

        return resultFuture;
    }

    private ListenableFuture<ArrayList<Registration>> getFullRegistrationInformation(String pnsHandle) {
        final SettableFuture<ArrayList<Registration>> resultFuture = SettableFuture.create();

        if (isNullOrWhiteSpace(pnsHandle)) {
            resultFuture.setException(new IllegalArgumentException("pnsHandle"));
            return resultFuture;
        }

        // get existing registrations
        String path = PNS_API_URL + "/registrations/";

        List<Pair<String, String>> requestHeaders = new ArrayList<Pair<String, String>>();
        List<Pair<String, String>> parameters = new ArrayList<Pair<String, String>>();
        parameters.add(new Pair<String, String>("platform", mPnsSpecificRegistrationFactory.getPlatform()));
        parameters.add(new Pair<String, String>("deviceId", pnsHandle));
        requestHeaders.add(new Pair<String, String>(HTTP.CONTENT_TYPE, MobileServiceConnection.JSON_CONTENTTYPE));

        ListenableFuture<ServiceFilterResponse> serviceFilterFuture = mHttpClient.request(path, null, "GET",
                requestHeaders, parameters);

        Futures.addCallback(serviceFilterFuture, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ServiceFilterResponse response) {
                ArrayList<Registration> registrationsList = new ArrayList<Registration>();

                JsonArray registrations = new JsonParser().parse(response.getContent()).getAsJsonArray();

                for (JsonElement registrationJson : registrations) {
                    Registration registration = null;
                    if (registrationJson.getAsJsonObject().has("templateName")) {
                        registration = mPnsSpecificRegistrationFactory
                                .parseTemplateRegistration(registrationJson.getAsJsonObject());
                    } else {
                        registration = mPnsSpecificRegistrationFactory
                                .parseNativeRegistration(registrationJson.getAsJsonObject());
                    }

                    registrationsList.add(registration);
                }

                resultFuture.set(registrationsList);
            }
        });

        return resultFuture;
    }

    /**
     * Creates a new registration in the server. If it exists, updates its
     * information
     *
     * @param registration The registration to create
     * @param callback     The operation callback
     */
    private ListenableFuture<String> registerInternal(final Registration registration) {

        final SettableFuture<String> resultFuture = SettableFuture.create();

        String pNSHandle = mSharedPreferences.getString(STORAGE_PREFIX + PNS_HANDLE_KEY, "");

        if (isNullOrWhiteSpace(pNSHandle)) {
            pNSHandle = registration.getPNSHandle();
        }

        ListenableFuture<Void> refreshRegistrationInformationIfNeededFuture = refreshRegistrationInformationIfNeeded(
                pNSHandle);

        Futures.addCallback(refreshRegistrationInformationIfNeededFuture, new FutureCallback<Void>() {

            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(Void v) {

                Futures.addCallback(createRegistrationId(registration), new FutureCallback<String>() {
                    @Override
                    public void onFailure(Throwable exception) {
                        resultFuture.setException(exception);
                    }

                    @Override
                    public void onSuccess(String registrationId) {
                        resultFuture.set(registrationId);
                    }
                });
            }
        });

        return resultFuture;
    }

    private ListenableFuture<String> createRegistrationId(final Registration registration) {

        String registrationId = null;

        final SettableFuture<String> resultFuture = SettableFuture.create();

        try {
            registrationId = retrieveRegistrationId(registration.getName());
        } catch (Exception e) {
            resultFuture.setException(e);
            return resultFuture;
        }

        if (isNullOrWhiteSpace(registrationId)) {
            return createRegistrationIdInternal(registration);
        } else {
            ListenableFuture<Void> unregisterInternalFuture = unregisterInternal(registration.getName());

            Futures.addCallback(unregisterInternalFuture, new FutureCallback<Void>() {

                @Override
                public void onFailure(Throwable exception) {
                    resultFuture.setException(exception);
                }

                @Override
                public void onSuccess(Void v) {

                    ListenableFuture<String> createRegistrationIdInternalFuture = createRegistrationIdInternal(
                            registration);

                    Futures.addCallback(createRegistrationIdInternalFuture, new FutureCallback<String>() {

                        @Override
                        public void onFailure(Throwable exception) {
                            resultFuture.setException(exception);
                        }

                        @Override
                        public void onSuccess(String registrationId) {
                            resultFuture.set(registrationId);
                        }
                    });
                }
            });
        }

        return resultFuture;
    }

    private ListenableFuture<String> createRegistrationIdInternal(final Registration registration) {

        final SettableFuture<String> resultFuture = SettableFuture.create();

        ListenableFuture<String> createRegistrationIdFuture = createRegistrationId();

        Futures.addCallback(createRegistrationIdFuture, new FutureCallback<String>() {

            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(String registrationId) {
                ListenableFuture<String> setRegistrationIdFuture = setRegistrationId(registration, registrationId);

                Futures.addCallback(setRegistrationIdFuture, new FutureCallback<String>() {

                    @Override
                    public void onFailure(Throwable exception) {
                        resultFuture.setException(exception);
                    }

                    @Override
                    public void onSuccess(String registrationId) {
                        resultFuture.set(registrationId);
                    }
                });
            }
        });

        return resultFuture;
    }

    private ListenableFuture<String> setRegistrationId(final Registration registration,
            final String registrationId) {
        registration.setRegistrationId(registrationId);

        final SettableFuture<String> resultFuture = SettableFuture.create();

        ListenableFuture<Void> upsertRegistrationInternalFuture = upsertRegistrationInternal(registration);

        Futures.addCallback(upsertRegistrationInternalFuture, new FutureCallback<Void>() {
            @Override
            public void onFailure(Throwable exception) {

                if (!(exception instanceof MobileServiceException)) {
                    resultFuture.setException(exception);
                }

                MobileServiceException mobileServiceException = (MobileServiceException) exception;

                ServiceFilterResponse response = mobileServiceException.getResponse();

                if (response != null && response.getStatus().getStatusCode() == 410) {

                    // if we get an RegistrationGoneException (410) from
                    // service, we will recreate registration id and will try to
                    // do upsert one more time.
                    // This can occur if the backing NotificationHub is changed
                    // or if the registration expires.

                    try {
                        removeRegistrationId(registration.getName());
                    } catch (Exception e) {
                        resultFuture.setException(e);
                        return;
                    }

                    ListenableFuture<String> createRegistrationIdFuture = createRegistrationId();

                    Futures.addCallback(createRegistrationIdFuture, new FutureCallback<String>() {
                        @Override
                        public void onFailure(Throwable exception) {
                            resultFuture.setException(exception);
                        }

                        @Override
                        public void onSuccess(final String registrationId) {
                            ListenableFuture<Void> upsertRegistrationInternalFuture2 = upsertRegistrationInternal(
                                    registration);

                            Futures.addCallback(upsertRegistrationInternalFuture2, new FutureCallback<Void>() {
                                @Override
                                public void onFailure(Throwable exception) {

                                    if (!(exception instanceof MobileServiceException)) {
                                        resultFuture.setException(exception);
                                    }

                                    MobileServiceException mobileServiceException = (MobileServiceException) exception;

                                    ServiceFilterResponse response = mobileServiceException.getResponse();

                                    if (response != null && response.getStatus().getStatusCode() == 410) {

                                        RegistrationGoneException registrationGoneException = new RegistrationGoneException(
                                                mobileServiceException);
                                        resultFuture.setException(registrationGoneException);
                                    }

                                }

                                public void onSuccess(Void v) {
                                    try {
                                        storeRegistrationId(registration.getName(),
                                                registration.getRegistrationId(), registration.getPNSHandle());
                                    } catch (Exception exception) {
                                        resultFuture.setException(exception);
                                        return;
                                    }
                                    resultFuture.set(registrationId);
                                }
                            });
                        }
                    });
                } else {
                    resultFuture.setException(exception);
                }
            }

            @Override
            public void onSuccess(Void v) {
                try {
                    storeRegistrationId(registration.getName(), registration.getRegistrationId(),
                            registration.getPNSHandle());
                } catch (Exception exception) {
                    resultFuture.setException(exception);
                    return;
                }

                resultFuture.set(registrationId);
            }
        });

        return resultFuture;
    }

    /**
     * Deletes a registration and removes it from local storage
     *
     * @param registrationName The registration name
     * @param callback         The operation callback
     * @throws Exception
     */
    private ListenableFuture<Void> unregisterInternal(String registrationName) {
        String registrationId = null;

        registrationId = retrieveRegistrationId(registrationName);

        return deleteRegistrationInternal(registrationName, registrationId);
    }

    private ListenableFuture<String> createRegistrationId() {

        String path = PNS_API_URL + "/registrationids/";

        final SettableFuture<String> resultFuture = SettableFuture.create();
        ListenableFuture<ServiceFilterResponse> serviceFilterFuture = mHttpClient.request(path, null, "POST", null,
                null);

        Futures.addCallback(serviceFilterFuture, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ServiceFilterResponse response) {
                for (Header header : response.getHeaders()) {
                    if (header.getName().equalsIgnoreCase(NEW_REGISTRATION_LOCATION_HEADER)) {

                        URI regIdUri = null;
                        try {
                            regIdUri = new URI(header.getValue());
                        } catch (URISyntaxException e) {
                            resultFuture.setException(e);

                            return;
                        }

                        String[] pathFragments = regIdUri.getPath().split("/");
                        String result = pathFragments[pathFragments.length - 1];

                        resultFuture.set(result);
                    }
                }
            }
        });

        return resultFuture;
    }

    /**
     * Updates a registration
     *
     * @param registration The registration to update
     * @param callback     The operation callback
     * @throws java.io.UnsupportedEncodingException
     * @throws Exception
     */
    private ListenableFuture<Void> upsertRegistrationInternal(final Registration registration) {

        final SettableFuture<Void> resultFuture = SettableFuture.create();

        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder = gsonBuilder.excludeFieldsWithoutExposeAnnotation();

        Gson gson = gsonBuilder.create();

        String resource = registration.getURI();
        String path = PNS_API_URL + resource;
        JsonElement json = gson.toJsonTree(registration);
        String body = json.toString();

        byte[] content;
        try {
            content = body.getBytes(MobileServiceClient.UTF8_ENCODING);
        } catch (UnsupportedEncodingException e) {
            resultFuture.setException(e);
            return resultFuture;
        }

        List<Pair<String, String>> requestHeaders = new ArrayList<Pair<String, String>>();

        requestHeaders.add(new Pair<String, String>(HTTP.CONTENT_TYPE, MobileServiceConnection.JSON_CONTENTTYPE));

        ListenableFuture<ServiceFilterResponse> serviceFilterFuture = mHttpClient.request(path, content, "PUT",
                requestHeaders, null);

        Futures.addCallback(serviceFilterFuture, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ServiceFilterResponse response) {
                resultFuture.set(null);
            }
        });

        return resultFuture;
    }

    /**
     * Deletes a registration and removes it from local storage
     *
     * @param registrationName The registration Name
     * @param registrationId   The registration Id
     * @param callback         The operation callback
     * @throws java.util.concurrent.ExecutionException
     * @throws InterruptedException
     */
    private ListenableFuture<Void> deleteRegistrationInternal(final String registrationName,
            final String registrationId) {

        final SettableFuture<Void> resultFuture = SettableFuture.create();

        if (isNullOrWhiteSpace(registrationId)) {
            resultFuture.set(null);
            return resultFuture;
        }

        String path = PNS_API_URL + "/registrations/" + registrationId;

        ListenableFuture<ServiceFilterResponse> serviceFilterFuture = mHttpClient.request(path, null, "DELETE",
                null, null);

        Futures.addCallback(serviceFilterFuture, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable exception) {
                resultFuture.setException(exception);
            }

            @Override
            public void onSuccess(ServiceFilterResponse response) {
                removeRegistrationId(registrationName);

                resultFuture.set(null);
            }
        });

        return resultFuture;
    }

    /**
     * Retrieves the registration id associated to the registration name from
     * local storage
     *
     * @param registrationName The registration name to look for in local storage
     * @return A registration id String
     * @throws Exception
     */
    private String retrieveRegistrationId(String registrationName) {
        return mSharedPreferences.getString(STORAGE_PREFIX + REGISTRATION_NAME_STORAGE_KEY + registrationName,
                null);
    }

    /**
     * Stores the registration name and id association in local storage
     *
     * @param registrationName The registration name to store in local storage
     * @param registrationId   The registration id to store in local storage
     * @throws Exception
     */
    private void storeRegistrationId(String registrationName, String registrationId, String pNSHandle) {
        Editor editor = mSharedPreferences.edit();

        editor.putString(STORAGE_PREFIX + REGISTRATION_NAME_STORAGE_KEY + registrationName, registrationId);

        editor.putString(STORAGE_PREFIX + PNS_HANDLE_KEY, pNSHandle);

        // Always overwrite the storage version with the latest value
        editor.putString(STORAGE_PREFIX + STORAGE_VERSION_KEY, STORAGE_VERSION);

        editor.commit();
    }

    /**
     * Removes the registration name and id association from local storage
     *
     * @param registrationName The registration name of the association to remove from local
     *                         storage
     * @throws Exception
     */
    private void removeRegistrationId(String registrationName) {
        Editor editor = mSharedPreferences.edit();

        editor.remove(STORAGE_PREFIX + REGISTRATION_NAME_STORAGE_KEY + registrationName);

        editor.commit();
    }

    private void removeAllRegistrationsId() {
        Editor editor = mSharedPreferences.edit();

        Set<String> keys = mSharedPreferences.getAll().keySet();

        for (String key : keys) {
            if (key.startsWith(STORAGE_PREFIX + REGISTRATION_NAME_STORAGE_KEY)) {
                editor.remove(key);
            }
        }

        editor.commit();
    }

    private void verifyStorageVersion() {
        String currentStorageVersion = mSharedPreferences.getString(STORAGE_PREFIX + STORAGE_VERSION_KEY, "");

        if (!currentStorageVersion.equals(STORAGE_VERSION)) {

            Editor editor = mSharedPreferences.edit();

            Set<String> keys = mSharedPreferences.getAll().keySet();

            for (String key : keys) {
                if (key.startsWith(STORAGE_PREFIX)) {
                    editor.remove(key);
                }
            }

            editor.commit();

            mIsRefreshNeeded = true;
        }

    }

    private class SyncState {
        public boolean alreadyReturn;
        public int size;
    }
}