Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fcm): Implement sendEach, sendEachAsync, sendEachForMulticast and sendEachForMulticastAsync (#785) #815

Merged
merged 6 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -482,5 +482,11 @@
<version>2.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
221 changes: 210 additions & 11 deletions src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.internal.CallableOperation;
import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
* This class is the entry point for all server-side Firebase Cloud Messaging actions.
Expand Down Expand Up @@ -91,7 +95,7 @@ public String send(@NonNull Message message) throws FirebaseMessagingException {
*
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
* FCM performs all the necessary validations and emulates the send operation. The {@code dryRun}
* option is useful for determining whether an FCM registration has been deleted. However, it
* option is useful for determining whether an FCM registration has been deleted. However, it
Doris-Ge marked this conversation as resolved.
Show resolved Hide resolved
* cannot be used to validate APNs tokens.
*
* @param message A non-null {@link Message} to be sent.
Expand Down Expand Up @@ -139,6 +143,191 @@ protected String execute() throws FirebaseMessagingException {
};
}

/**
* Sends each message in the given list via Firebase Cloud Messaging.
* Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the
* given array.
*
* <p>The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return
* value is in the same order as the input list.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Doris-Ge marked this conversation as resolved.
Show resolved Hide resolved
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
* failure, meaning that none of the messages in the list could be sent. Partial failures or
* no failures are only indicated by a {@link BatchResponse}.
*/
public BatchResponse sendEach(@NonNull List<Message> messages) throws FirebaseMessagingException {
return sendEachOp(messages, false).call();
}


/**
* Sends each message in the given list via Firebase Cloud Messaging.
* Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the
* given array.
*
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
* FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
* option is useful for determining whether an FCM registration has been deleted. But it cannot be
* used to validate APNs tokens.
*
* <p>The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return
* value is in the same order as the input list.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Doris-Ge marked this conversation as resolved.
Show resolved Hide resolved
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
* failure, meaning that none of the messages in the list could be sent. Partial failures or
* no failures are only indicated by a {@link BatchResponse}.
*/
public BatchResponse sendEach(
@NonNull List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
return sendEachOp(messages, dryRun).call();
}

/**
* Similar to {@link #sendEach(List)} but performs the operation asynchronously.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages) {
return sendEachOp(messages, false).callAsync(app);
}

/**
* Similar to {@link #sendEach(List, boolean)} but performs the operation asynchronously.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages, boolean dryRun) {
return sendEachOp(messages, dryRun).callAsync(app);
}

private CallableOperation<BatchResponse, FirebaseMessagingException> sendEachOp(
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() <= 500,
"messages list must not contain more than 500 elements");

return new CallableOperation<BatchResponse, FirebaseMessagingException>() {
@Override
protected BatchResponse execute() throws FirebaseMessagingException {
List<ApiFuture<SendResponse>> list = new ArrayList<>();
for (Message message : immutableMessages) {
ApiFuture<SendResponse> messageId = sendOpForSendResponse(message, dryRun).callAsync(app);
list.add(messageId);
}
try {
List<SendResponse> responses = ApiFutures.allAsList(list).get();
return new BatchResponseImpl(responses);
} catch (InterruptedException | ExecutionException e) {
throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID);
}
}
};
}

private CallableOperation<SendResponse, FirebaseMessagingException> sendOpForSendResponse(
final Message message, final boolean dryRun) {
checkNotNull(message, "message must not be null");
final FirebaseMessagingClient messagingClient = getMessagingClient();
return new CallableOperation<SendResponse, FirebaseMessagingException>() {
@Override
protected SendResponse execute() {
try {
String messageId = messagingClient.send(message, dryRun);
return SendResponse.fromMessageId(messageId);
} catch (FirebaseMessagingException e) {
return SendResponse.fromException(e);
}
}
};
}

/**
* Sends the given multicast message to all the FCM registration tokens specified in it.
*
* <p>This method uses the {@link #sendEach(List)} API under the hood to send the given
* message to all the target recipients. The list of responses obtained by calling
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
* tokens in the {@link MulticastMessage}.
*
* @param message A non-null {@link MulticastMessage}
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Doris-Ge marked this conversation as resolved.
Show resolved Hide resolved
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
* failure, meaning that none of the messages in the list could be sent. Partial failures or
* no failures are only indicated by a {@link BatchResponse}.
*/
public BatchResponse sendEachForMulticast(
@NonNull MulticastMessage message) throws FirebaseMessagingException {
return sendEachForMulticast(message, false);
}

/**
* Sends the given multicast message to all the FCM registration tokens specified in it.
*
* <p>If the {@code dryRun} option is set to true, the message will not be actually sent. Instead
* FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun}
* option is useful for determining whether an FCM registration has been deleted. But it cannot be
* used to validate APNs tokens.
*
* <p>This method uses the {@link #sendEach(List)} API under the hood to send the given
* message to all the target recipients. The list of responses obtained by calling
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
* tokens in the {@link MulticastMessage}.
*
* @param message A non-null {@link MulticastMessage}.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Doris-Ge marked this conversation as resolved.
Show resolved Hide resolved
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
* failure, meaning that none of the messages in the list could be sent. Partial failures or
* no failures are only indicated by a {@link BatchResponse}.
*/
public BatchResponse sendEachForMulticast(@NonNull MulticastMessage message, boolean dryRun)
throws FirebaseMessagingException {
checkNotNull(message, "multicast message must not be null");
return sendEach(message.getMessageList(), dryRun);
}

/**
* Similar to {@link #sendEachForMulticast(MulticastMessage)} but performs the operation
* asynchronously.
*
* @param message A non-null {@link MulticastMessage}.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
public ApiFuture<BatchResponse> sendEachForMulticastAsync(@NonNull MulticastMessage message) {
return sendEachForMulticastAsync(message, false);
}

/**
* Similar to {@link #sendEachForMulticast(MulticastMessage, boolean)} but performs the operation
* asynchronously.
*
* @param message A non-null {@link MulticastMessage}.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
public ApiFuture<BatchResponse> sendEachForMulticastAsync(
@NonNull MulticastMessage message, boolean dryRun) {
checkNotNull(message, "multicast message must not be null");
return sendEachAsync(message.getMessageList(), dryRun);
}

/**
* Sends all the messages in the given list via Firebase Cloud Messaging. Employs batching to
* send the entire list as a single RPC call. Compared to the {@link #send(Message)} method, this
Expand All @@ -150,8 +339,10 @@ protected String execute() throws FirebaseMessagingException {
* @param messages A non-null, non-empty list containing up to 500 messages.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here indicates a total failure -- i.e. none of the messages in the
* list could be sent. Partial failures are indicated by a {@link BatchResponse} return value.
* delivery. An exception here indicates a total failure, meaning that none of the messages in
* the list could be sent. Partial failures are indicated by a {@link BatchResponse} return
* value.
* @deprecated Use {@link #sendEach(List)} instead.
*/
public BatchResponse sendAll(
@NonNull List<Message> messages) throws FirebaseMessagingException {
Expand All @@ -175,8 +366,10 @@ public BatchResponse sendAll(
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here indicates a total failure -- i.e. none of the messages in the
* list could be sent. Partial failures are indicated by a {@link BatchResponse} return value.
* delivery. An exception here indicates a total failure, meaning that none of the messages in
* the list could be sent. Partial failures are indicated by a {@link BatchResponse} return
* value.
* @deprecated Use {@link #sendEach(List, boolean)} instead.
*/
public BatchResponse sendAll(
@NonNull List<Message> messages, boolean dryRun) throws FirebaseMessagingException {
Expand All @@ -187,8 +380,9 @@ public BatchResponse sendAll(
* Similar to {@link #sendAll(List)} but performs the operation asynchronously.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
* @deprecated Use {@link #sendEachAsync(List)} instead.
*/
public ApiFuture<BatchResponse> sendAllAsync(@NonNull List<Message> messages) {
return sendAllAsync(messages, false);
Expand All @@ -199,8 +393,9 @@ public ApiFuture<BatchResponse> sendAllAsync(@NonNull List<Message> messages) {
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent, or when the emulation has finished.
* @deprecated Use {@link #sendEachAsync(List, boolean)} instead.
*/
public ApiFuture<BatchResponse> sendAllAsync(
@NonNull List<Message> messages, boolean dryRun) {
Expand All @@ -218,9 +413,10 @@ public ApiFuture<BatchResponse> sendAllAsync(
* @param message A non-null {@link MulticastMessage}
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here indicates a total failure -- i.e. the messages could not be
* delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
* delivery. An exception here indicates a total failure, meaning that the messages could not
* be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
* return value.
* @deprecated Use {@link #sendEachForMulticast(MulticastMessage)} instead.
*/
public BatchResponse sendMulticast(
@NonNull MulticastMessage message) throws FirebaseMessagingException {
Expand All @@ -244,9 +440,10 @@ public BatchResponse sendMulticast(
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here indicates a total failure -- i.e. the messages could not be
* delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
* delivery. An exception here indicates a total failure, meaning that the messages could not
* be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse}
* return value.
* @deprecated Use {@link #sendEachForMulticast(MulticastMessage, boolean)} instead.
*/
public BatchResponse sendMulticast(
@NonNull MulticastMessage message, boolean dryRun) throws FirebaseMessagingException {
Expand All @@ -261,6 +458,7 @@ public BatchResponse sendMulticast(
* @param message A non-null {@link MulticastMessage}.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
* @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage)} instead.
*/
public ApiFuture<BatchResponse> sendMulticastAsync(@NonNull MulticastMessage message) {
return sendMulticastAsync(message, false);
Expand All @@ -274,6 +472,7 @@ public ApiFuture<BatchResponse> sendMulticastAsync(@NonNull MulticastMessage mes
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
* @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage, boolean)} instead.
*/
public ApiFuture<BatchResponse> sendMulticastAsync(
@NonNull MulticastMessage message, boolean dryRun) {
Expand Down
Loading