Package com.google.android.c2dm.server

Source Code of com.google.android.c2dm.server.C2DMessaging

/*
* Copyright 2010 Google Inc.
*
* 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.
*/

package com.google.android.c2dm.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskHandle;
import com.google.appengine.api.labs.taskqueue.TaskOptions;

/**
*/
@SuppressWarnings("serial")
public class C2DMessaging {
    private static final String UPDATE_CLIENT_AUTH = "Update-Client-Auth";

    public static final String C2DM_SEND_ENDPOINT =
        "https://android.clients.google.com/c2dm/send";

    private static final Logger log = Logger.getLogger(C2DMessaging.class.getName());

    public static final String PARAM_REGISTRATION_ID = "registration_id";

    public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";
   
    public static final String PARAM_COLLAPSE_KEY = "collapse_key";

    private static final String UTF8 = "UTF-8";
   
    /**
     * Jitter - random interval to wait before retry.
     */
    public static final int C2DM_MAX_JITTER_MSEC = 3000;

    static C2DMessaging singleton;

    final C2DMConfigLoader serverConfig;
   
    private C2DMessaging(C2DMConfigLoader serverConfig) {
        this.serverConfig = serverConfig;
    }
   
    public synchronized static C2DMessaging get(ServletContext servletContext) {
        if (singleton == null) {
            C2DMConfigLoader serverConfig = new C2DMConfigLoader(getPMF(servletContext));
            singleton = new C2DMessaging(serverConfig);
        }
        return singleton;
    }
   
    public synchronized static C2DMessaging get(PersistenceManagerFactory pmf) {
        if (singleton == null) {
            C2DMConfigLoader serverConfig = new C2DMConfigLoader(pmf);
            singleton = new C2DMessaging(serverConfig);
        }
        return singleton;
    }
   
    C2DMConfigLoader getServerConfig() {
        return serverConfig;
    }

    /**
     * Initialize PMF - we use a context attribute, so other servlets can
     * be share the same instance. This is similar with a shared static
     * field, but avoids dependencies.
     */
    public static PersistenceManagerFactory getPMF(ServletContext ctx) {
        PersistenceManagerFactory pmfFactory =
            (PersistenceManagerFactory) ctx.getAttribute(
                    PersistenceManagerFactory.class.getName());
        if (pmfFactory == null) {
            pmfFactory = JDOHelper
                .getPersistenceManagerFactory("transactions-optional");
            ctx.setAttribute(
                    PersistenceManagerFactory.class.getName(),
                    pmfFactory);
        }
        return pmfFactory;
}

   
    public boolean sendNoRetry(String registrationId,
            String collapse,
            Map<String, String[]> params,
            boolean delayWhileIdle)
        throws IOException {
       
        // Send a sync message to this Android device.
        StringBuilder postDataBuilder = new StringBuilder();
        postDataBuilder.append(PARAM_REGISTRATION_ID).
            append("=").append(registrationId);

        if (delayWhileIdle) {
            postDataBuilder.append("&")
                .append(PARAM_DELAY_WHILE_IDLE).append("=1");
        }
        postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=").
            append(collapse);

        for (Object keyObj: params.keySet()) {
            String key = (String) keyObj;
            if (key.startsWith("data.")) {
                String[] values = (String[]) params.get(key);
                postDataBuilder.append("&").append(key).append("=").
                    append(URLEncoder.encode(values[0], UTF8));
            }
        }

        byte[] postData = postDataBuilder.toString().getBytes(UTF8);

        // Hit the dm URL.
        URL url = new URL(C2DM_SEND_ENDPOINT);
       
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setRequestProperty("Content-Length", Integer.toString(postData.length));
        String authToken = serverConfig.getToken();
        conn.setRequestProperty("Authorization", "GoogleLogin auth=" + authToken);

        OutputStream out = conn.getOutputStream();
        out.write(postData);
        out.close();
       
        int responseCode = conn.getResponseCode();
       
        if (responseCode == HttpServletResponse.SC_UNAUTHORIZED ||
                responseCode == HttpServletResponse.SC_FORBIDDEN) {
            // The token is too old - return false to retry later, will fetch the token
            // from DB. This happens if the password is changed or token expires. Either admin
            // is updating the token, or Update-Client-Auth was received by another server,
            // and next retry will get the good one from database.
            log.warning("Unauthorized - need token");
            serverConfig.invalidateCachedToken();
            return false;
        }
       
        // Check for updated token header
        String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH);
        if (updatedAuthToken != null && !authToken.equals(updatedAuthToken)) {
            log.info("Got updated auth token from C2DM servers: " +
                    updatedAuthToken);
            serverConfig.updateToken(updatedAuthToken);
        }
           
        String responseLine = new BufferedReader(new InputStreamReader(conn.getInputStream()))
            .readLine();
           
        // NOTE: You *MUST* use exponential backoff if you receive a 503 response code.
        // Since App Engine's task queue mechanism automatically does this for tasks that
        // return non-success error codes, this is not explicitly implemented here.
        // If we weren't using App Engine, we'd need to manually implement this.
        log.info("Got " + responseCode + " response from Google C2DM endpoint.");
       
        if (responseLine == null || responseLine.equals("")) {
            throw new IOException("Got empty response from Google C2DM endpoint.");
        }

        String[] responseParts = responseLine.split("=", 2);
        if (responseParts.length != 2) {
            log.warning("Invalid message from google: " +
                    responseCode + " " + responseLine);
            throw new IOException("Invalid response from Google " +
                    responseCode + " " + responseLine);
        }

        if (responseParts[0].equals("id")) {
            log.info("Successfully sent data message to device: " + responseLine);
            return true;
        }
       
        if (responseParts[0].equals("Error")) {
            String err = responseParts[1];
            log.warning("Got error response from Google C2DM endpoint: " + err);
            // No retry.
            // TODO(costin): show a nicer error to the user.
            throw new IOException("Server error: " + err);
        } else {
            // 500 or unparseable response - server error, needs to retry
            log.warning("Invalid response from google " + responseLine + " " +
                    responseCode);
            return false;
        }
    }

    /**
     * Helper method to send a message, with 2 parameters.
     *
     * Permanent errors will result in IOException.
     * Retriable errors will cause the message to be scheduled for retry.
     */
    public void sendWithRetry(String token, String collapseKey,
            String name1, String value1, String name2, String value2)
                throws IOException {

        Map<String, String[]> params = new HashMap<String, String[]>();
        params.put("data." + name1, new String[] {value1});
        params.put("data." + name2, new String[] {value2});
       
        boolean sentOk = sendNoRetry(token, collapseKey, params, true);
        if (!sentOk) {
            retry(token, collapseKey, params, true);
        }
    }

    public boolean sendNoRetry(String token, String collapseKey,
            String name1, String value1, String name2, String value2)
                throws IOException {

        Map<String, String[]> params = new HashMap<String, String[]>();
        params.put("data." + name1, new String[] {value1});
        params.put("data." + name2, new String[] {value2});
       
        try {
            return sendNoRetry(token, collapseKey, params, true);
        } catch (IOException ex) {
            return false;
        }
    }
   
    private void retry(String token, String collapseKey,
            Map<String, String[]> params, boolean delayWhileIdle) {
        Queue dmQueue = QueueFactory.getQueue("c2dm");
        try {
            TaskOptions url =
                TaskOptions.Builder.url(C2DMRetryServlet.URI)
                .param(C2DMessaging.PARAM_REGISTRATION_ID, token)
                .param(C2DMessaging.PARAM_COLLAPSE_KEY, collapseKey);
            if (delayWhileIdle) {
                url.param(PARAM_DELAY_WHILE_IDLE, "1");
            }
            for (String key: params.keySet()) {
                String[] values = (String[]) params.get(key);
                url.param(key, URLEncoder.encode(values[0], UTF8));
            }
           
            // Task queue implements the exponential backoff
            long jitter = (int) Math.random() * C2DM_MAX_JITTER_MSEC;
            url.countdownMillis(jitter);
           
            TaskHandle add = dmQueue.add(url);
        } catch (UnsupportedEncodingException e) {
            // Ignore - UTF8 should be supported
            log.log(Level.SEVERE, "Unexpected error", e);
        }
       
    }
       
}
TOP

Related Classes of com.google.android.c2dm.server.C2DMessaging

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.