Android Open Source - wonderpush-android-sdk Wonder Push Rest Client






From Project

Back to project page wonderpush-android-sdk.

License

The source code is released under:

Apache License

If you think the Android project wonderpush-android-sdk listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.wonderpush.sdk;
//from   w w  w  .j ava  2  s  .  c om
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.Header;
import org.apache.http.NoHttpResponseException;
import org.apache.http.message.BasicHeader;
import org.json.JSONException;
import org.json.JSONObject;

import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Base64;
import android.util.Log;

import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.wonderpush.sdk.WonderPush.Response;
import com.wonderpush.sdk.WonderPush.ResponseHandler;

/**
 * A REST client that lets you hit the WonderPush REST server.
 */
class WonderPushRestClient {

    private static final String TAG = WonderPushRestClient.class.getSimpleName();

    private enum HttpMethod {
        GET, PUT, POST, DELETE
    }

    private static final int RETRY_INTERVAL_BAD_AUTH = 1 * 1000; // in milliseconds
    private static final int RETRY_INTERVAL = 30 * 1000; // in milliseconds
    private static final int RETRY_INTERVAL_NETWORK_ISSUE = 60 * 1000; // in milliseconds
    private static boolean sIsFetchingAnonymousAccessToken = false;
    private static List<WonderPush.ResponseHandler> sPendingHandlers = new ArrayList<WonderPush.ResponseHandler>();

    private static AsyncHttpClient sClient = new AsyncHttpClient();

    /**
     * A GET request
     *
     * @param resource
     *            The resource path, starting with /
     * @param params
     *            AsyncHttpClient request parameters
     * @param responseHandler
     *            An AsyncHttpClient response handler
     */
    protected static void get(String resource, WonderPush.RequestParams params, WonderPush.ResponseHandler responseHandler) {
        requestAuthenticated(new Request(HttpMethod.GET, resource, params, responseHandler));
    }

    /**
     * A POST request
     *
     * @param resource
     *            The resource path, starting with /
     * @param params
     *            AsyncHttpClient request parameters
     * @param responseHandler
     *            An AsyncHttpClient response handler
     */
    protected static void post(String resource, WonderPush.RequestParams params, WonderPush.ResponseHandler responseHandler) {
        requestAuthenticated(new Request(HttpMethod.POST, resource, params, responseHandler));
    }

    /**
     * A POST request that is guaranteed to be executed when a network connection
     * is present, surviving application reboot. The responseHandler will be
     * called only if the network is present when the request is first run.
     *
     * @param resource
     * @param params
     * @param responseHandler
     */
    protected static void postEventually(String resource, WonderPush.RequestParams params,
            final WonderPush.ResponseHandler responseHandler) {

        // Create a request
        final Request request = new Request(HttpMethod.POST, resource, params, null);

        // Wrap the provided handler with ours
        request.setHandler(new WonderPush.ResponseHandler() {
            @Override
            public void onFailure(Throwable e, Response errorResponse) {
                // Post to vault on network error
                if (e instanceof NoHttpResponseException
                        || e instanceof UnknownHostException
                        || e instanceof SocketException) {
                    // retry later
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                WonderPushRequestVault.getDefaultVault().put(request);
                            } catch (JSONException e1) {
                                Log.e(TAG, "Could not save request to vault", e1);
                            }
                        }
                    }, RETRY_INTERVAL_NETWORK_ISSUE);
                    return;
                }

                // Forward to original handler otherwise
                if (null != responseHandler) {
                    responseHandler.onFailure(e, errorResponse);
                }
            }

            @Override
            public void onSuccess(Response response) {
                if (null != responseHandler) {
                    responseHandler.onSuccess(response);
                }
            }

            @Override
            public void onSuccess(int statusCode, Response response) {
                if (null != responseHandler) {
                    responseHandler.onSuccess(statusCode, response);
                }
            }
        });

        requestAuthenticated(request);
    }

    /**
     * A PUT request
     *
     * @param resource
     *            The resource path, starting with /
     * @param params
     *            AsyncHttpClient request parameters
     * @param responseHandler
     *            An AsyncHttpClient response handler
     */
    protected static void put(String resource, WonderPush.RequestParams params, WonderPush.ResponseHandler responseHandler) {
        requestAuthenticated(new Request(HttpMethod.PUT, resource, params, responseHandler));
    }

    /**
     * A DELETE request
     *
     * @param resource
     *            The resource path, starting with /
     * @param params
     *            AsyncHttpClient request parameters
     * @param responseHandler
     *            An AsyncHttpClient response handler
     */
    protected static void delete(String resource, WonderPush.ResponseHandler responseHandler) {
        requestAuthenticated(new Request(HttpMethod.DELETE, resource, null, responseHandler));
    }

    /**
     * If no access token is found in the user's preferences, fetch an anonymous access token.
     *
     * @param onFetchedHandler
     *            A handler called if a request to fetch an access token has been
     *            executed successfully, never called if retreived from cache
     * @return Whether or not a request has been executed to fetch an anonymous
     *         access token (true fetching, false retrived from local cache)
     */
    protected static boolean fetchAnonymousAccessTokenIfNeeded(WonderPush.ResponseHandler onFetchedHandler) {
        if (null == WonderPushConfiguration.getAccessToken()) {
            fetchAnonymousAccessToken(onFetchedHandler);
            return true;
        }
        return false;
    }

    /**
     * If no access token is found in the user's preferences, fetch an anonymous access token.
     */
    protected static void fetchAnonymousAccessTokenIfNeeded() {
        fetchAnonymousAccessTokenIfNeeded(null);
    }

    /**
     * Runs the specified request and ensure a valid access token is fetched if
     * necessary beforehand, or afterwards (and re-run the request) if the request
     * fails for auth reasons.
     *
     * @param request
     */
    protected static void requestAuthenticated(final Request request) {
        if (null == request) {
            return;
        }

        String accessToken = WonderPushConfiguration.getAccessToken();

        // User is authenticated
        if (accessToken == null) {
            // User is not authenticated, request a token
            fetchAnonymousAccessTokenAndRunRequest(request);
            return;
        }

        // Add the access token to the params
        WonderPush.RequestParams params = request.getParams();
        if (null == params) {
            params = new WonderPush.RequestParams();
            request.setParams(params);
        }

        params.remove("accessToken");
        params.put("accessToken", accessToken);

        // Wrap the request handler with our own
        WonderPush.ResponseHandler wrapperHandler = new WonderPush.ResponseHandler() {
            @Override
            public void onSuccess(int status, WonderPush.Response response) {
                if (request.getHandler() != null) {
                    request.getHandler().onSuccess(status, response);
                }
            }

            @Override
            public void onFailure(Throwable e, WonderPush.Response errorResponse) {
                WonderPush.logError("Request failed", e);
                if (errorResponse != null && WonderPush.ERROR_INVALID_ACCESS_TOKEN == errorResponse.getErrorCode()) {
                    // null out the access token
                    WonderPushConfiguration.setAccessToken(null);
                    WonderPushConfiguration.setSID(null);
                    WonderPushConfiguration.setInstallationId(null);

                    // retry later now
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            requestAuthenticated(request);
                        }
                    }, RETRY_INTERVAL_BAD_AUTH);
//                } else if (e instanceof ConnectTimeoutException) {
//                    // retry later
//                    new Handler().postDelayed(new Runnable() {
//                        @Override
//                        public void run() {
//                            requestAuthenticated(request);
//                        }
//                    }, RETRY_INTERVAL_NETWORK_ISSUE);
                } else {
                    if (request.getHandler() != null) {
                        request.getHandler().onFailure(e, errorResponse);
                    }
                }
            }

            @Override
            public void onSuccess(Response response) {
                if (request.getHandler() != null) {
                    request.getHandler().onSuccess(response);
                }
            }
        };
        Request wrapperRequest = (Request) request.clone();
        wrapperRequest.setHandler(wrapperHandler);

        // Perform request
        request(wrapperRequest);
    }

    /**
     * Thin wrapper to the {@link AsyncHttpClient} library.
     *
     * @param request
     */
    private static void request(final Request request) {
        if (null == request) {
            WonderPush.logError("Request with null request.");
            return;
        }

        // Decorate parameters
        WonderPushRequestParamsDecorator.decorate(request.getResource(), request.getParams());

        // Generate signature
        Header authorizationHeader = request.getAuthorizationHeader();

        // Headers
        Header[] headers = null;
        if (null != authorizationHeader) {
            headers = new Header[1];
            headers[0] = authorizationHeader;
        }

        String url = WonderPushUriHelper.getAbsoluteUrl(request.getResource());
        WonderPush.logDebug("requesting url: " + request.getMethod() + " " + url + "?" + request.getParams().getURLEncodedString());
        // TODO: support other contentTypes such as "application/json"
        String contentType = "application/x-www-form-urlencoded";

        // Handler
        final ResponseHandler handler = request.getHandler();
        final long sendDate = SystemClock.elapsedRealtime();
        JsonHttpResponseHandler jsonHandler = new JsonHttpResponseHandler() {
            @Override
            public void onFailure(Throwable e, JSONObject data) {
                syncTime(data);
                if (data != null) {
                    WonderPush.logDebug("Requesting Error: " + data);
                    WonderPush.setNetworkAvailable(true);
                    if (handler != null) {
                        handler.onFailure(e, new WonderPush.Response(data));
                    }
                } else {
                    WonderPush.setNetworkAvailable(false);
                    if (handler != null) {
                        handler.onFailure(e, null);
                    }
                }
            }

            @Override
            public void onFailure(Throwable e, String data) {
                WonderPush.setNetworkAvailable(false);
                if (handler != null) {
                    handler.onFailure(e, null);
                }
            }

            @Override
            public void onSuccess(int statusCode, JSONObject data) {
                syncTime(data);
                WonderPush.setNetworkAvailable(true);
                if (handler != null) {
                    handler.onSuccess(statusCode, new WonderPush.Response(data));
                }
            }

            private void syncTime(JSONObject data) {
                long recvDate = SystemClock.elapsedRealtime();
                if (data == null || !data.has("_serverTime")) {
                    return;
                }
                WonderPush.syncTimeWithServer(sendDate, recvDate, data.optLong("_serverTime"), data.optLong("_serverTook"));
            }
        };
        // NO UNNECESSARY WORK HERE, because of timed request
        switch (request.getMethod()) {
            case GET:
                sClient.get(null, url, headers, request.getParams(), jsonHandler);
                break;
            case PUT:
                sClient.put(null, url, headers,
                        request.getParams() != null ? request.getParams().getEntity() : null,
                        contentType, jsonHandler);
                break;
            case POST:
                sClient.post(null, url, headers, request.getParams(), contentType, jsonHandler);
                break;
            case DELETE:
                sClient.delete(null, url, headers, jsonHandler);
                break;
        }
    }

    protected static void fetchAnonymousAccessToken(final WonderPush.ResponseHandler handler) {
        fetchAnonymousAccessToken(handler, 0);
    }

    protected static void fetchAnonymousAccessToken(final WonderPush.ResponseHandler handler, final int nbRetries) {
        if (sIsFetchingAnonymousAccessToken) {
            queueHandler(handler);
            return;
        }
        sIsFetchingAnonymousAccessToken = true;
        WonderPush.RequestParams authParams = new WonderPush.RequestParams();
        authParams.put("clientId", WonderPush.getClientId());
        authParams.put("devicePlatform", "Android");
        authParams.put("deviceModel", WonderPush.getDeviceModel());
        String udid = WonderPush.getUDID();
        if (null != udid) {
            authParams.put("deviceId", udid);
        }
        String userId = WonderPushConfiguration.getUserId();
        if (null != userId) {
            authParams.put("userId", userId);
        }

        String resource = "/authentication/accessToken";

        request(new Request(HttpMethod.POST, resource, authParams,
                new WonderPush.ResponseHandler() {
                    @Override
                    public void onFailure(Throwable e, Response errorResponse) {
                        if (nbRetries <= 0) {
                            Log.e(TAG, "Error request anonymous access token (aborting): " + (errorResponse != null ? errorResponse.getJSONObject().toString() : "null error response, aborting"), e);
                            if (errorResponse != null && WonderPush.ERROR_INVALID_CREDENTIALS == errorResponse.getErrorCode()) {
                                Log.e(TAG, "Check your clientId/clientSecret couple");
                            }

                            sIsFetchingAnonymousAccessToken = false;
                            if (null != handler) {
                                handler.onFailure(e, errorResponse);
                            }
                            WonderPush.ResponseHandler chainedHandler = null;
                            while ((chainedHandler = dequeueHandler()) != null) {
                                chainedHandler.onFailure(e, errorResponse);
                            }
                            return;
                        }
                        Log.e(TAG, "Error request anonymous access token (retrying: " + nbRetries + "): " + (errorResponse != null ? errorResponse.getJSONObject().toString() : "null error response, retrying"), e);

                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                WonderPush.logDebug("re-requesting access token!");

                                sIsFetchingAnonymousAccessToken = false;
                                fetchAnonymousAccessToken(handler, nbRetries - 1);
                            }
                        }, RETRY_INTERVAL);
                    }

                    @Override
                    public void onSuccess(int statusCode, Response response) {
                        // Parse response
                        JSONObject json = response.getJSONObject();
                        if (json.has("token") && json.has("data")) {
                            String token = json.optString("token", null);
                            JSONObject data = json.optJSONObject("data");
                            if (data != null && data.has("installationId")) {
                                String sid = data.optString("sid", null);
                                String installationId = data.optString("installationId", null);
                                String userId = data.optString("userId", null);

                                // Store access token
                                WonderPushConfiguration.setAccessToken(token);
                                WonderPushConfiguration.setSID(sid);
                                WonderPushConfiguration.setInstallationId(installationId);
                                WonderPushConfiguration.setUserId(userId);
                                sIsFetchingAnonymousAccessToken = false;

                                // call handlers
                                if (null != handler) {
                                    handler.onSuccess(statusCode, response);
                                }
                                WonderPush.ResponseHandler chainedHandler = null;
                                while ((chainedHandler = dequeueHandler()) != null) {
                                    chainedHandler.onSuccess(statusCode, response);
                                }
                                return;
                            }
                        }
                        Log.e(TAG, "Could not obtain anonymous access token from server");
                    }

                    @Override
                    public void onSuccess(Response response) {
                        this.onSuccess(200, response);
                    }
                }
        ));
    }

    /**
     * Fetches an anonymous access token and run the given request with that token.
     * Retries when access token cannot be fetched.
     *
     * @param request
     *            The request to be run
     */
    protected static void fetchAnonymousAccessTokenAndRunRequest(final Request request) {
        fetchAnonymousAccessToken(new WonderPush.ResponseHandler() {
            @Override
            public void onSuccess(Response response) {
                requestAuthenticated(request);
            }

            @Override
            public void onFailure(Throwable e, Response errorResponse) {
            }
        });
    }

    private static void queueHandler(WonderPush.ResponseHandler handler) {
        if (null == handler) {
            return;
        }

        synchronized (sPendingHandlers) {
            sPendingHandlers.add(handler);
        }
    }

    private static WonderPush.ResponseHandler dequeueHandler() {
        WonderPush.ResponseHandler handler = null;
        synchronized (sPendingHandlers) {
            if (sPendingHandlers.size() > 0) {
                handler = sPendingHandlers.get(0);
                if (null != handler) {
                    sPendingHandlers.remove(0);
                }
            }
        }
        return handler;
    }

    /**
     * A serializable object that represents a request to the WonderPush API.
     */
    protected static class Request implements Cloneable {

        HttpMethod mMethod;
        WonderPush.RequestParams mParams;
        WonderPush.ResponseHandler mHandler;
        String mResource;

        public Request(HttpMethod method, String resource, WonderPush.RequestParams params, WonderPush.ResponseHandler handler) {
            mMethod = method;
            mParams = params;
            mHandler = handler;
            mResource = resource;
        }

        public Request(JSONObject data) throws JSONException {
            mMethod = HttpMethod.values()[data.getInt("method")];
            mResource = data.getString("resource");
            JSONObject paramsJson = data.getJSONObject("params");
            mParams = new WonderPush.RequestParams();
            @SuppressWarnings("unchecked")
            Iterator<String> keys = paramsJson.keys();
            while (keys.hasNext()) {
                String key = keys.next();
                mParams.put(key, paramsJson.getString(key));
            }
        }

        public JSONObject toJSON() throws JSONException {
            JSONObject result = new JSONObject();
            result.put("method", mMethod.ordinal());
            result.put("resource", mResource);
            JSONObject params = new JSONObject();
            if (null != mParams) {
                for (String key : mParams.getParamNames()) {
                    params.put(key, mParams.getParamValue(key));
                }
            }
            result.put("params", params);
            return result;
        }

        public HttpMethod getMethod() {
            return mMethod;
        }

        public WonderPush.RequestParams getParams() {
            return mParams;
        }

        public WonderPush.ResponseHandler getHandler() {
            return mHandler;
        }

        public String getResource() {
            return mResource;
        }

        public void setMethod(HttpMethod mMethod) {
            this.mMethod = mMethod;
        }

        public void setParams(WonderPush.RequestParams mParams) {
            this.mParams = mParams;
        }

        public void setHandler(WonderPush.ResponseHandler mHandler) {
            this.mHandler = mHandler;
        }

        public void setResource(String resource) {
            this.mResource = resource;
        }

        @Override
        protected Object clone() {
            return new Request(mMethod, mResource, mParams, mHandler);
        }

        /**
         * Generates X-WonderPush-Authorization header with request signature
         *
         * @return The authorization header or null for GET requests
         */
        protected BasicHeader getAuthorizationHeader() {
            try {
                StringBuilder sb = new StringBuilder();

                // Step 1: add HTTP method uppercase
                switch (mMethod) {
                    case POST:
                        sb.append("POST");
                        break;
                    case PUT:
                        sb.append("PUT");
                        break;
                    case GET:
                        // No authorization header for GET requests
                        return null;
                    case DELETE:
                        sb.append("DELETE");
                        break;
                }

                sb.append('&');

                // Step 2: add the URI
                Uri uri = Uri.parse(mResource);

                // Query string is stripped from resource
                sb.append(encode(String.format("%s%s", WonderPush.getBaseURL(),
                        uri.getEncodedPath())));

                // Step 3: add URL encoded parameters
                sb.append('&');
                TreeSet<String> paramNames = new TreeSet<String>();

                // Params from the URL
                WonderPush.RequestParams queryStringParams = QueryStringParser.getRequestParams(uri.getQuery());
                if (queryStringParams != null) {
                    paramNames.addAll(queryStringParams.getParamNames());
                }

                // Params from the request
                if (mParams != null) {
                    paramNames.addAll(mParams.getParamNames());
                }

                if (paramNames.size() > 0) {
                    String last = paramNames.last();
                    for (String paramName : paramNames) {
                        String paramValue = null;

                        if (null != mParams) {
                            paramValue = mParams.getParamValue(paramName);
                        }

                        if (paramValue == null && queryStringParams != null) {
                            paramValue = queryStringParams.getParamValue(paramName);
                        }

                        sb.append(encode(String.format("%s=%s", encode(paramName), encode(paramValue))));
                        if (!last.equals(paramName)) {
                            sb.append("%26");
                        }
                    }
                }

                // Step 4: add body
                sb.append('&');
                // TODO: add the body here when we support other content types than application/x-www-form-urlencoded
                Mac mac = Mac.getInstance("HmacSHA1");
                SecretKeySpec secret = new SecretKeySpec(WonderPush.getClientSecret().getBytes("UTF-8"), mac.getAlgorithm());
                mac.init(secret);
                byte[] digest = mac.doFinal(sb.toString().getBytes());
                String sig = Base64.encodeToString(digest, Base64.DEFAULT).trim();
                String encodedSig = encode(sig.trim());
                BasicHeader result = new BasicHeader("X-WonderPush-Authorization", String.format("WonderPush sig=\"%s\", meth=\"0\"", encodedSig));

                return result;
            } catch (Exception e) {
                Log.e(TAG, "Could not generate signature", e);
                return null;
            }
        }

        private static String encode(String s) throws UnsupportedEncodingException {
            return URLEncoder.encode(s, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        }

        @Override
        public String toString() {
            String method = null;
            switch (mMethod) {
                case POST:
                    method = "POST";
                    break;
                case PUT:
                    method = "PUT";
                    break;
                case GET:
                    method = "GET";
                    break;
                case DELETE:
                    method = "DELETE";
            }
            return String.format("%s %s", method, mResource);
        }

    }

}




Java Source Code List

com.wonderpush.sdk.QueryStringParser.java
com.wonderpush.sdk.WonderPushBroadcastReceiver.java
com.wonderpush.sdk.WonderPushCompatibilityHelper.java
com.wonderpush.sdk.WonderPushConfiguration.java
com.wonderpush.sdk.WonderPushDialogBuilder.java
com.wonderpush.sdk.WonderPushGcmClient.java
com.wonderpush.sdk.WonderPushInitializer.java
com.wonderpush.sdk.WonderPushJobQueue.java
com.wonderpush.sdk.WonderPushOnUpgradeReceiver.java
com.wonderpush.sdk.WonderPushRequestParamsDecorator.java
com.wonderpush.sdk.WonderPushRequestVault.java
com.wonderpush.sdk.WonderPushRestClient.java
com.wonderpush.sdk.WonderPushService.java
com.wonderpush.sdk.WonderPushUriHelper.java
com.wonderpush.sdk.WonderPushView.java
com.wonderpush.sdk.WonderPush.java
com.wonderpush.sdk.package-info.java