Skip to content

Commit bd798b5

Browse files
committed
Implement sendEach, sendEachAsync, sendEachForMulticast and sendEachForMulticastAsync
`sendEach` vs `sendAll` 1. `sendEach` sends one HTTP request to V1 Send endpoint for each message in the array. `sendAll` sends only one HTTP request to V1 Batch Send endpoint to send all messages in the array. 2. `sendEach` calls `messagingClient.send` to send each message and constructs a `SendResponse` with the returned `messageId`. If `messagingClient.send` throws out an exception, `sendEach` will catch the exception and also turn it into a `SendResponse` with the exception in it. `sendEach` calls `ApiFutures.allAsList().get()` to execute all `messagingClient.send` calls asynchronously and wait for all of them to complete and construct a `BatchResponse` with all `SendResponse`s. Therefore, unlike `sendAll`, `sendEach` does not always throw an error for a total failure. It can also return a `BatchResponse` with only errors in it. `sendEachForMulticast` calls `sendEach` under the hood. `sendEachAsync` is the async version of `sendEach`. `sendEachForMulticastAsync` is the async version of `sendEachForMulticast`.
1 parent e8c4087 commit bd798b5

File tree

2 files changed

+506
-4
lines changed

2 files changed

+506
-4
lines changed

src/main/java/com/google/firebase/messaging/FirebaseMessaging.java

+200-3
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,22 @@
2020
import static com.google.common.base.Preconditions.checkNotNull;
2121

2222
import com.google.api.core.ApiFuture;
23+
import com.google.api.core.ApiFutures;
2324
import com.google.common.annotations.VisibleForTesting;
2425
import com.google.common.base.Strings;
2526
import com.google.common.base.Supplier;
2627
import com.google.common.base.Suppliers;
2728
import com.google.common.collect.ImmutableList;
29+
import com.google.firebase.ErrorCode;
2830
import com.google.firebase.FirebaseApp;
2931
import com.google.firebase.ImplFirebaseTrampolines;
3032
import com.google.firebase.internal.CallableOperation;
3133
import com.google.firebase.internal.FirebaseService;
3234
import com.google.firebase.internal.NonNull;
3335

36+
import java.util.ArrayList;
3437
import java.util.List;
38+
import java.util.concurrent.ExecutionException;
3539

3640
/**
3741
* This class is the entry point for all server-side Firebase Cloud Messaging actions.
@@ -91,7 +95,7 @@ public String send(@NonNull Message message) throws FirebaseMessagingException {
9195
*
9296
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
9397
* FCM performs all the necessary validations and emulates the send operation. The {@code dryRun}
94-
* option is useful for determining whether an FCM registration has been deleted. However, it
98+
* option is useful for determining whether an FCM registration has been deleted. However, it
9599
* cannot be used to validate APNs tokens.
96100
*
97101
* @param message A non-null {@link Message} to be sent.
@@ -139,6 +143,191 @@ protected String execute() throws FirebaseMessagingException {
139143
};
140144
}
141145

146+
/**
147+
* Sends each message in the given list via Firebase Cloud Messaging.
148+
* Unlike {@link #sendAll(List)}, this method makes a single HTTP call for each message in the
149+
* given array.
150+
*
151+
* <p>The responses list obtained by calling {@link BatchResponse#getResponses()} on the return
152+
* value corresponds to the order of input messages.
153+
*
154+
* @param messages A non-null, non-empty list containing up to 500 messages.
155+
* @return A {@link BatchResponse} indicating the result of the operation.
156+
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
157+
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
158+
* failure -- i.e. none of the messages in the list could be sent. Partial failures or no
159+
* failures are only indicated by a {@link BatchResponse}.
160+
*/
161+
public BatchResponse sendEach(@NonNull List<Message> messages) throws FirebaseMessagingException {
162+
return sendEachOp(messages, false).call();
163+
}
164+
165+
166+
/**
167+
* Sends each message in the given list via Firebase Cloud Messaging.
168+
* Unlike {@link #sendAll(List)}, this method makes a single HTTP call for each message in the
169+
* given array.
170+
*
171+
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
172+
* FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
173+
* option is useful for determining whether an FCM registration has been deleted. But it cannot be
174+
* used to validate APNs tokens.
175+
*
176+
* <p>The responses list obtained by calling {@link BatchResponse#getResponses()} on the return
177+
* value corresponds to the order of input messages.
178+
*
179+
* @param messages A non-null, non-empty list containing up to 500 messages.
180+
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
181+
* @return A {@link BatchResponse} indicating the result of the operation.
182+
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
183+
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
184+
* failure -- i.e. none of the messages in the list could be sent. Partial failures or no
185+
* failures are only indicated by a {@link BatchResponse}.
186+
*/
187+
public BatchResponse sendEach(
188+
@NonNull List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
189+
return sendEachOp(messages, dryRun).call();
190+
}
191+
192+
/**
193+
* Similar to {@link #sendEach(List)} but performs the operation asynchronously.
194+
*
195+
* @param messages A non-null, non-empty list containing up to 500 messages.
196+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
197+
* the messages have been sent.
198+
*/
199+
public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages) {
200+
return sendEachOp(messages, false).callAsync(app);
201+
}
202+
203+
/**
204+
* Similar to {@link #sendEach(List, boolean)} but performs the operation asynchronously.
205+
*
206+
* @param messages A non-null, non-empty list containing up to 500 messages.
207+
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
208+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
209+
* the messages have been sent.
210+
*/
211+
public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages, boolean dryRun) {
212+
return sendEachOp(messages, dryRun).callAsync(app);
213+
}
214+
215+
private CallableOperation<BatchResponse, FirebaseMessagingException> sendEachOp(
216+
final List<Message> messages, final boolean dryRun) {
217+
final List<Message> immutableMessages = ImmutableList.copyOf(messages);
218+
checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty");
219+
checkArgument(immutableMessages.size() <= 500,
220+
"messages list must not contain more than 500 elements");
221+
222+
return new CallableOperation<BatchResponse, FirebaseMessagingException>() {
223+
@Override
224+
protected BatchResponse execute() throws FirebaseMessagingException {
225+
List<ApiFuture<SendResponse>> list = new ArrayList<>();
226+
for (Message message : immutableMessages) {
227+
ApiFuture<SendResponse> messageId = sendOpForSendResponse(message, dryRun).callAsync(app);
228+
list.add(messageId);
229+
}
230+
try {
231+
List<SendResponse> responses = ApiFutures.allAsList(list).get();
232+
return new BatchResponseImpl(responses);
233+
} catch (InterruptedException | ExecutionException e) {
234+
throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID);
235+
}
236+
}
237+
};
238+
}
239+
240+
private CallableOperation<SendResponse, FirebaseMessagingException> sendOpForSendResponse(
241+
final Message message, final boolean dryRun) {
242+
checkNotNull(message, "message must not be null");
243+
final FirebaseMessagingClient messagingClient = getMessagingClient();
244+
return new CallableOperation<SendResponse, FirebaseMessagingException>() {
245+
@Override
246+
protected SendResponse execute() {
247+
try {
248+
String messageId = messagingClient.send(message, dryRun);
249+
return SendResponse.fromMessageId(messageId);
250+
} catch (FirebaseMessagingException e) {
251+
return SendResponse.fromException(e);
252+
}
253+
}
254+
};
255+
}
256+
257+
/**
258+
* Sends the given multicast message to all the FCM registration tokens specified in it.
259+
*
260+
* <p>This method uses the {@link #sendEach(List)} API under the hood to send the given
261+
* message to all the target recipients. The responses list obtained by calling
262+
* {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens
263+
* in the {@link MulticastMessage}.
264+
*
265+
* @param message A non-null {@link MulticastMessage}
266+
* @return A {@link BatchResponse} indicating the result of the operation.
267+
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
268+
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
269+
* failure -- i.e. none of the messages in the list could be sent. Partial failures or no
270+
* failures are only indicated by a {@link BatchResponse}.
271+
*/
272+
public BatchResponse sendEachForMulticast(
273+
@NonNull MulticastMessage message) throws FirebaseMessagingException {
274+
return sendEachForMulticast(message, false);
275+
}
276+
277+
/**
278+
* Sends the given multicast message to all the FCM registration tokens specified in it.
279+
*
280+
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
281+
* FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
282+
* option is useful for determining whether an FCM registration has been deleted. But it cannot be
283+
* used to validate APNs tokens.
284+
*
285+
* <p>This method uses the {@link #sendEach(List)} API under the hood to send the given
286+
* message to all the target recipients. The responses list obtained by calling
287+
* {@link BatchResponse#getResponses()} on the return value corresponds to the order of tokens
288+
* in the {@link MulticastMessage}.
289+
*
290+
* @param message A non-null {@link MulticastMessage}.
291+
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
292+
* @return A {@link BatchResponse} indicating the result of the operation.
293+
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
294+
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
295+
* failure -- i.e. none of the messages in the list could be sent. Partial failures or no
296+
* failures are only indicated by a {@link BatchResponse}.
297+
*/
298+
public BatchResponse sendEachForMulticast(@NonNull MulticastMessage message, boolean dryRun)
299+
throws FirebaseMessagingException {
300+
checkNotNull(message, "multicast message must not be null");
301+
return sendEach(message.getMessageList(), dryRun);
302+
}
303+
304+
/**
305+
* Similar to {@link #sendEachForMulticast(MulticastMessage)} but performs the operation
306+
* asynchronously.
307+
*
308+
* @param message A non-null {@link MulticastMessage}.
309+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
310+
* the messages have been sent.
311+
*/
312+
public ApiFuture<BatchResponse> sendEachForMulticastAsync(@NonNull MulticastMessage message) {
313+
return sendEachForMulticastAsync(message, false);
314+
}
315+
316+
/**
317+
* Similar to {@link #sendEachForMulticast(MulticastMessage, boolean)} but performs the operation
318+
* asynchronously.
319+
*
320+
* @param message A non-null {@link MulticastMessage}.
321+
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
322+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
323+
* the messages have been sent.
324+
*/
325+
public ApiFuture<BatchResponse> sendEachForMulticastAsync(
326+
@NonNull MulticastMessage message, boolean dryRun) {
327+
checkNotNull(message, "multicast message must not be null");
328+
return sendEachAsync(message.getMessageList(), dryRun);
329+
}
330+
142331
/**
143332
* Sends all the messages in the given list via Firebase Cloud Messaging. Employs batching to
144333
* send the entire list as a single RPC call. Compared to the {@link #send(Message)} method, this
@@ -152,6 +341,7 @@ protected String execute() throws FirebaseMessagingException {
152341
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
153342
* delivery. An exception here indicates a total failure -- i.e. none of the messages in the
154343
* list could be sent. Partial failures are indicated by a {@link BatchResponse} return value.
344+
* @deprecated Use {@link #sendEach(List)} instead.
155345
*/
156346
public BatchResponse sendAll(
157347
@NonNull List<Message> messages) throws FirebaseMessagingException {
@@ -177,6 +367,7 @@ public BatchResponse sendAll(
177367
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
178368
* delivery. An exception here indicates a total failure -- i.e. none of the messages in the
179369
* list could be sent. Partial failures are indicated by a {@link BatchResponse} return value.
370+
* @deprecated Use {@link #sendEach(List, boolean)} instead.
180371
*/
181372
public BatchResponse sendAll(
182373
@NonNull List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
@@ -187,8 +378,9 @@ public BatchResponse sendAll(
187378
* Similar to {@link #sendAll(List)} but performs the operation asynchronously.
188379
*
189380
* @param messages A non-null, non-empty list containing up to 500 messages.
190-
* @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
381+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
191382
* the messages have been sent.
383+
* @deprecated Use {@link #sendEachAsync(List)} instead.
192384
*/
193385
public ApiFuture<BatchResponse> sendAllAsync(@NonNull List<Message> messages) {
194386
return sendAllAsync(messages, false);
@@ -199,8 +391,9 @@ public ApiFuture<BatchResponse> sendAllAsync(@NonNull List<Message> messages) {
199391
*
200392
* @param messages A non-null, non-empty list containing up to 500 messages.
201393
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
202-
* @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
394+
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
203395
* the messages have been sent, or when the emulation has finished.
396+
* @deprecated Use {@link #sendEachAsync(List, boolean)} instead.
204397
*/
205398
public ApiFuture<BatchResponse> sendAllAsync(
206399
@NonNull List<Message> messages, boolean dryRun) {
@@ -221,6 +414,7 @@ public ApiFuture<BatchResponse> sendAllAsync(
221414
* delivery. An exception here indicates a total failure -- i.e. the messages could not be
222415
* delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
223416
* return value.
417+
* @deprecated Use {@link #sendEachForMulticast(MulticastMessage)} instead.
224418
*/
225419
public BatchResponse sendMulticast(
226420
@NonNull MulticastMessage message) throws FirebaseMessagingException {
@@ -247,6 +441,7 @@ public BatchResponse sendMulticast(
247441
* delivery. An exception here indicates a total failure -- i.e. the messages could not be
248442
* delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
249443
* return value.
444+
* @deprecated Use {@link #sendEachForMulticast(MulticastMessage, boolean)} instead.
250445
*/
251446
public BatchResponse sendMulticast(
252447
@NonNull MulticastMessage message, boolean dryRun) throws FirebaseMessagingException {
@@ -261,6 +456,7 @@ public BatchResponse sendMulticast(
261456
* @param message A non-null {@link MulticastMessage}.
262457
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
263458
* the messages have been sent.
459+
* @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage)} instead.
264460
*/
265461
public ApiFuture<BatchResponse> sendMulticastAsync(@NonNull MulticastMessage message) {
266462
return sendMulticastAsync(message, false);
@@ -274,6 +470,7 @@ public ApiFuture<BatchResponse> sendMulticastAsync(@NonNull MulticastMessage mes
274470
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
275471
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
276472
* the messages have been sent.
473+
* @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage, boolean)} instead.
277474
*/
278475
public ApiFuture<BatchResponse> sendMulticastAsync(
279476
@NonNull MulticastMessage message, boolean dryRun) {

0 commit comments

Comments
 (0)