Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b53c94f
Preliminary impl of FCM batch support
hiranya911 Jan 29, 2019
6882354
Added BatchMessage class and tests
hiranya911 Jan 29, 2019
e75bcff
Refactored the FCM send operation
hiranya911 Jan 29, 2019
726ee09
Added unit tests for sendBatch() API
hiranya911 Jan 29, 2019
e3ad436
Refactored error handling
hiranya911 Jan 29, 2019
504e9ef
Refactored FCM logic into a new FirebaseMessagingClient class
hiranya911 Jan 30, 2019
80ed4b1
Using a separate request factory for child requests
hiranya911 Jan 30, 2019
057fcb5
Added the InstanceIdClient class for handling topic mgt ops
hiranya911 Jan 30, 2019
2c36d4c
Added license headers
hiranya911 Jan 30, 2019
c437042
Renamed BatchResponse as SendResponse
hiranya911 Jan 30, 2019
a3223fd
Added BatchResponse class; Renamed BatchMessage to MulticastMessage
hiranya911 Feb 6, 2019
5668421
Updated tests and docs
hiranya911 Feb 6, 2019
693711f
Updated documentation
hiranya911 Feb 6, 2019
46507d8
Added documentation and tests
hiranya911 Feb 6, 2019
b4d80e0
Merge branch 'master' into hkj-fcm-batch
hiranya911 Feb 6, 2019
0371159
Updated docs, changelog and annotations
hiranya911 Feb 6, 2019
5d57e07
Updated integration test to use multiple topics; Updated docs
hiranya911 Feb 7, 2019
f8f8c1e
Removing a redundant whitespace
hiranya911 Feb 11, 2019
7ecabe0
Reduced FCM batch size to 100 (#250)
hiranya911 Feb 19, 2019
3c0ae40
Setting the X-Client-Version header for FCM (#252)
hiranya911 Feb 22, 2019
c6367c0
Snippets for FCM sendAll() and sendMulticast() (#249)
hiranya911 Feb 25, 2019
1fbddcb
Addressing some documentation nits
hiranya911 Mar 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactored the FCM send operation
  • Loading branch information
hiranya911 committed Jan 29, 2019
commit e75bcff3fba4a3d217cc7fdf9cb7107e6957bd70
236 changes: 125 additions & 111 deletions src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,42 @@ public ApiFuture<String> sendAsync(@NonNull Message message, boolean dryRun) {
return sendOp(message, dryRun).callAsync(app);
}

public List<BatchResponse> sendBatch(List<Message> messages) throws FirebaseMessagingException {
return sendBatch(messages, false);
}

public List<BatchResponse> sendBatch(
BatchMessage batch, boolean dryRun) throws FirebaseMessagingException {
checkNotNull(batch, "batch message must not be null");
return sendBatch(batch.getMessageList(), dryRun);
}

public List<BatchResponse> sendBatch(BatchMessage batch) throws FirebaseMessagingException {
return sendBatch(batch, false);
}

public List<BatchResponse> sendBatch(
List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
return sendBatchOp(messages, dryRun).call();
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(BatchMessage batch) {
return sendBatchAsync(batch, false);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(BatchMessage batch, boolean dryRun) {
checkNotNull(batch, "batch message must not be null");
return sendBatchAsync(batch.getMessageList(), dryRun);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(List<Message> messages) {
return sendBatchAsync(messages, false);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(List<Message> messages, boolean dryRun) {
return sendBatchOp(messages, dryRun).callAsync(app);
}

/**
* Subscribes a list of registration tokens to a topic.
*
Expand Down Expand Up @@ -223,82 +259,55 @@ public ApiFuture<TopicManagementResponse> unsubscribeFromTopicAsync(
return manageTopicOp(registrationTokens, topic, IID_UNSUBSCRIBE_PATH).callAsync(app);
}

public List<BatchResponse> sendBatch(List<Message> messages) throws FirebaseMessagingException {
return sendBatch(messages, false);
}

public List<BatchResponse> sendBatch(
BatchMessage batch, boolean dryRun) throws FirebaseMessagingException {
checkNotNull(batch, "batch message must not be null");
return sendBatch(batch.getMessageList(), dryRun);
}

public List<BatchResponse> sendBatch(BatchMessage batch) throws FirebaseMessagingException {
return sendBatch(batch, false);
}

public List<BatchResponse> sendBatch(
List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
return sendBatchOp(messages, dryRun).call();
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(BatchMessage batch) {
return sendBatchAsync(batch, false);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(BatchMessage batch, boolean dryRun) {
checkNotNull(batch, "batch message must not be null");
return sendBatchAsync(batch.getMessageList(), dryRun);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(List<Message> messages) {
return sendBatchAsync(messages, false);
}

public ApiFuture<List<BatchResponse>> sendBatchAsync(List<Message> messages, boolean dryRun) {
return sendBatchOp(messages, dryRun).callAsync(app);
}

private CallableOperation<String, FirebaseMessagingException> sendOp(
final Message message, final boolean dryRun) {
checkNotNull(message, "message must not be null");
return new CallableOperation<String, FirebaseMessagingException>() {
@Override
protected String execute() throws FirebaseMessagingException {
ImmutableMap.Builder<String, Object> payload = ImmutableMap.<String, Object>builder()
.put("message", message);
if (dryRun) {
payload.put("validate_only", true);
}
HttpResponse response = null;
try {
HttpRequest request = requestFactory.buildPostRequest(
new GenericUrl(url), new JsonHttpContent(jsonFactory, payload.build()));
request.getHeaders().set("X-GOOG-API-FORMAT-VERSION", "2");
request.setParser(new JsonObjectParser(jsonFactory));
request.setResponseInterceptor(interceptor);
response = request.execute();
MessagingServiceResponse parsed = new MessagingServiceResponse();
jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed);
return parsed.getName();
return sendSingleRequest(message, dryRun);
} catch (HttpResponseException e) {
handleSendHttpError(e);
return null;
} catch (IOException e) {
throw new FirebaseMessagingException(
INTERNAL_ERROR, "Error while calling FCM backend service", e);
} finally {
disconnectQuietly(response);
}
}
};
}

private String sendSingleRequest(Message message, boolean dryRun) throws IOException {
ImmutableMap.Builder<String, Object> payload = ImmutableMap.<String, Object>builder()
.put("message", message);
if (dryRun) {
payload.put("validate_only", true);
}
HttpRequest request = requestFactory.buildPostRequest(
new GenericUrl(url), new JsonHttpContent(this.jsonFactory, payload.build()));
this.setFcmApiFormatVersion(request.getHeaders());
request.setParser(new JsonObjectParser(this.jsonFactory));
request.setResponseInterceptor(this.interceptor);
HttpResponse response = request.execute();
try {
MessagingServiceResponse parsed = new MessagingServiceResponse();
this.jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed);
return parsed.getName();
} finally {
disconnectQuietly(response);
}
}

private void setFcmApiFormatVersion(HttpHeaders headers) {
headers.set("X-GOOG-API-FORMAT-VERSION", "2");
}

private void handleSendHttpError(HttpResponseException e) throws FirebaseMessagingException {
MessagingServiceErrorResponse response = new MessagingServiceErrorResponse();
if (e.getContent() != null) {
try {
JsonParser parser = jsonFactory.createJsonParser(e.getContent());
JsonParser parser = this.jsonFactory.createJsonParser(e.getContent());
parser.parseAndClose(response);
} catch (IOException ignored) {
// ignored
Expand All @@ -307,6 +316,63 @@ private void handleSendHttpError(HttpResponseException e) throws FirebaseMessagi
throw FirebaseMessagingException.fromErrorResponse(response, e);
}

private CallableOperation<List<BatchResponse>, FirebaseMessagingException> sendBatchOp(
final List<Message> messages, final boolean dryRun) {

final List<Message> immutableMessages = ImmutableList.copyOf(messages);
checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty");
checkArgument(immutableMessages.size() <= 1000,
"messages list must not contain more than 1000 elements");
return new CallableOperation<List<BatchResponse>,FirebaseMessagingException>() {
@Override
protected List<BatchResponse> execute() throws FirebaseMessagingException {
try {
return sendBatchRequest(immutableMessages, dryRun);
} catch (HttpResponseException e) {
handleSendHttpError(e);
return null;
} catch (IOException e) {
throw new FirebaseMessagingException(
INTERNAL_ERROR, "Error while calling FCM backend service", e);
}
}
};
}

private List<BatchResponse> sendBatchRequest(
List<Message> messages, boolean dryRun) throws IOException {

MessagingBatchCallback callback = new MessagingBatchCallback();
BatchRequest batch = newBatchRequest(messages, dryRun, callback);
batch.execute();
return callback.getResponses();
}

private BatchRequest newBatchRequest(
List<Message> messages, boolean dryRun, MessagingBatchCallback callback) throws IOException {

BatchRequest batch = new BatchRequest(
this.requestFactory.getTransport(), this.requestFactory.getInitializer());
batch.setBatchUrl(new GenericUrl(FCM_BATCH_URL));

JsonObjectParser jsonParser = new JsonObjectParser(this.jsonFactory);
for (Message message : messages) {
ImmutableMap.Builder<String, Object> payload = ImmutableMap.<String, Object>builder()
.put("message", message);
if (dryRun) {
payload.put("validate_only", true);
}
HttpRequest request = this.requestFactory.buildPostRequest(
new GenericUrl(this.url), new JsonHttpContent(this.jsonFactory, payload.build()));
request.setParser(jsonParser);
this.setFcmApiFormatVersion(request.getHeaders());
batch.queue(
request, MessagingServiceResponse.class, MessagingServiceErrorResponse.class, callback);
}

return batch;
}

private CallableOperation<TopicManagementResponse, FirebaseMessagingException>
manageTopicOp(
final List<String> registrationTokens, final String topic, final String path) {
Expand Down Expand Up @@ -376,62 +442,6 @@ private void handleTopicManagementHttpError(
throw new FirebaseMessagingException(code, msg, e);
}

private CallableOperation<List<BatchResponse>, FirebaseMessagingException> sendBatchOp(
final List<Message> messages, final boolean dryRun) {

final List<Message> immutableMessages = ImmutableList.copyOf(messages);
checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty");
checkArgument(immutableMessages.size() <= 1000,
"messages list must not contain more than 1000 elements");
return new CallableOperation<List<BatchResponse>,FirebaseMessagingException>() {
@Override
protected List<BatchResponse> execute() throws FirebaseMessagingException {
try {
return sendBatchRequest(immutableMessages, dryRun);
} catch (HttpResponseException e) {
handleSendHttpError(e);
return null;
} catch (IOException e) {
throw new FirebaseMessagingException(
INTERNAL_ERROR, "Error while calling FCM backend service", e);
}
}
};
}

private List<BatchResponse> sendBatchRequest(
List<Message> messages, boolean dryRun) throws IOException {

MessagingBatchCallback callback = new MessagingBatchCallback();
BatchRequest batch = newBatchRequest(messages, dryRun, callback);
batch.execute();
return ImmutableList.copyOf(callback.responses);
}

private BatchRequest newBatchRequest(
List<Message> messages, boolean dryRun, MessagingBatchCallback callback) throws IOException {

BatchRequest batch = new BatchRequest(
requestFactory.getTransport(), requestFactory.getInitializer());
batch.setBatchUrl(new GenericUrl(FCM_BATCH_URL));

for (Message message : messages) {
ImmutableMap.Builder<String, Object> payload = ImmutableMap.<String, Object>builder()
.put("message", message);
if (dryRun) {
payload.put("validate_only", true);
}
HttpRequest request = requestFactory.buildPostRequest(
new GenericUrl(url), new JsonHttpContent(jsonFactory, payload.build()));
request.getHeaders().set("X-GOOG-API-FORMAT-VERSION", "2");
request.setParser(new JsonObjectParser(jsonFactory));
batch.queue(
request, MessagingServiceResponse.class, MessagingServiceErrorResponse.class, callback);
}

return batch;
}

private static void disconnectQuietly(HttpResponse response) {
if (response != null) {
try {
Expand Down Expand Up @@ -492,7 +502,7 @@ private static class InstanceIdServiceErrorResponse {
private static class MessagingBatchCallback
implements BatchCallback<MessagingServiceResponse, MessagingServiceErrorResponse> {

private final List<BatchResponse> responses = new ArrayList<>();
private final ImmutableList.Builder<BatchResponse> responses = ImmutableList.builder();

@Override
public void onSuccess(
Expand All @@ -505,5 +515,9 @@ public void onFailure(
MessagingServiceErrorResponse error, HttpHeaders responseHeaders) throws IOException {
responses.add(BatchResponse.fromErrorResponse(error));
}

List<BatchResponse> getResponses() {
return this.responses.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public String getErrorCode() {
}
Object details = error.get("details");
if (details != null && details instanceof List) {
for (Object detail : (List) details) {
for (Object detail : (List<?>) details) {
if (detail instanceof Map) {
Map detailMap = (Map) detail;
Map<?,?> detailMap = (Map<?,?>) detail;
if (FCM_ERROR_TYPE.equals(detailMap.get("@type"))) {
return (String) detailMap.get("errorCode");
}
Expand Down