/*
* Copyright 2012 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.apps.iosched.gcm.server;
import com.google.android.gcm.server.Constants;
import com.google.android.gcm.server.Message;
import com.google.android.gcm.server.MulticastResult;
import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;
import com.google.appengine.api.datastore.Entity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet, called from an App Engine task, that sends the given message (sync or announcement)
* to all devices in a given multicast group (up to 1000).
*/
public class SendMessageServlet extends BaseServlet {
private static final int ANNOUNCEMENT_TTL = (int) TimeUnit.MINUTES.toSeconds(300);
private Sender sender;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
sender = newSender(config);
}
/**
* Creates the {@link Sender} based on the servlet settings.
*/
protected Sender newSender(ServletConfig config) {
String key = (String) config.getServletContext()
.getAttribute(ApiKeyInitializer.ATTRIBUTE_ACCESS_KEY);
return new Sender(key);
}
/**
* Processes the request to add a new message.
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String multicastKey = req.getParameter("multicastKey");
Entity multicast = Datastore.getMulticast(multicastKey);
@SuppressWarnings("unchecked")
List<String> devices = (List<String>)
multicast.getProperty(Datastore.MULTICAST_REG_IDS_PROPERTY);
String announcement = (String)
multicast.getProperty(Datastore.MULTICAST_ANNOUNCEMENT_PROPERTY);
// Build the GCM message
Message.Builder builder = new Message.Builder()
.delayWhileIdle(true);
if (announcement != null && announcement.trim().length() > 0) {
builder
.collapseKey("announcement")
.addData("announcement", announcement)
.timeToLive(ANNOUNCEMENT_TTL);
} else {
builder
.collapseKey("sync");
}
Message message = builder.build();
// Send the GCM message.
MulticastResult multicastResult;
try {
multicastResult = sender.sendNoRetry(message, devices);
logger.info("Result: " + multicastResult);
} catch (IOException e) {
logger.log(Level.SEVERE, "Exception posting " + message, e);
taskDone(resp, multicastKey);
return;
}
// check if any registration ids must be updated
if (multicastResult.getCanonicalIds() != 0) {
List<Result> results = multicastResult.getResults();
for (int i = 0; i < results.size(); i++) {
String canonicalRegId = results.get(i).getCanonicalRegistrationId();
if (canonicalRegId != null) {
String regId = devices.get(i);
Datastore.updateRegistration(regId, canonicalRegId);
}
}
}
boolean allDone = true;
if (multicastResult.getFailure() != 0) {
// there were failures, check if any could be retried
List<Result> results = multicastResult.getResults();
List<String> retryableRegIds = new ArrayList<String>();
for (int i = 0; i < results.size(); i++) {
String error = results.get(i).getErrorCodeName();
if (error != null) {
String regId = devices.get(i);
logger.warning("Got error (" + error + ") for regId " + regId);
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister it
Datastore.unregister(regId);
} else if (error.equals(Constants.ERROR_UNAVAILABLE)) {
retryableRegIds.add(regId);
}
}
}
if (!retryableRegIds.isEmpty()) {
// update task
Datastore.updateMulticast(multicastKey, retryableRegIds);
allDone = false;
retryTask(resp);
}
}
if (allDone) {
taskDone(resp, multicastKey);
} else {
retryTask(resp);
}
}
/**
* Indicates to App Engine that this task should be retried.
*/
private void retryTask(HttpServletResponse resp) {
resp.setStatus(500);
}
/**
* Indicates to App Engine that this task is done.
*/
private void taskDone(HttpServletResponse resp, String multicastKey) {
Datastore.deleteMulticast(multicastKey);
resp.setStatus(200);
}
}