diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/TopicSnippets.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/TopicSnippets.java new file mode 100644 index 000000000000..368481266a5f --- /dev/null +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/TopicSnippets.java @@ -0,0 +1,332 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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. + */ + +/* + * EDITING INSTRUCTIONS + * This file is referenced in Topic's javadoc. Any change to this file should be reflected in + * Topic's javadoc. + */ + +package com.google.cloud.examples.pubsub.snippets; + +import com.google.cloud.AsyncPage; +import com.google.cloud.Identity; +import com.google.cloud.Page; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.cloud.pubsub.SubscriptionId; +import com.google.cloud.pubsub.Topic; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +/** + * This class contains a number of snippets for the {@link Topic} class. + */ +public class TopicSnippets { + + private final Topic topic; + + public TopicSnippets(Topic topic) { + this.topic = topic; + } + + /** + * Example of getting the topic's latest information. + */ + // [TARGET reload()] + public Topic reload() { + // [START reload] + Topic latestTopic = topic.reload(); + if (latestTopic == null) { + // the topic was not found + } + // [END reload] + return latestTopic; + } + + /** + * Example of asynchronously getting the topic's latest information. + */ + // [TARGET reloadAsync()] + public Topic reloadAsync() throws ExecutionException, InterruptedException { + // [START reloadAsync] + Future future = topic.reloadAsync(); + // ... + Topic latestTopic = future.get(); + if (latestTopic == null) { + // the topic was not found + } + // [END reloadAsync] + return latestTopic; + } + + /** + * Example of deleting the topic. + */ + // [TARGET delete()] + public boolean delete() { + // [START delete] + boolean deleted = topic.delete(); + if (deleted) { + // the topic was deleted + } else { + // the topic was not found + } + // [END delete] + return deleted; + } + + /** + * Example of asynchronously deleting the topic. + */ + // [TARGET deleteAsync()] + public boolean deleteAsync() throws ExecutionException, InterruptedException { + // [START deleteAsync] + Future future = topic.deleteAsync(); + // ... + boolean deleted = future.get(); + if (deleted) { + // the topic was deleted + } else { + // the topic was not found + } + // [END deleteAsync] + return deleted; + } + + /** + * Example of publishing one message to the topic. + */ + // [TARGET publish(Message)] + public String publishOneMessage() { + // [START publishOneMessage] + Message message = Message.of("payload"); + String messageId = topic.publish(message); + // [END publishOneMessage] + return messageId; + } + + /** + * Example of asynchronously publishing one message to the topic. + */ + // [TARGET publishAsync(Message)] + public String publishOneMessageAsync() + throws ExecutionException, InterruptedException { + // [START publishOneMessageAsync] + Message message = Message.of("payload"); + Future future = topic.publishAsync(message); + // ... + String messageId = future.get(); + // [END publishOneMessageAsync] + return messageId; + } + + + /** + * Example of publishing a list of messages to the topic. + */ + // [TARGET publish(Iterable)] + public List publishMessageList() { + // [START publishMessageList] + List messages = new LinkedList<>(); + messages.add(Message.of("payload1")); + messages.add(Message.of("payload2")); + List messageIds = topic.publish(messages); + // [END publishMessageList] + return messageIds; + } + + /** + * Example of asynchronously publishing a list of messages to the topic. + */ + // [TARGET publishAsync(Iterable)] + public List publishMessageListAsync() + throws ExecutionException, InterruptedException { + // [START publishMessageListAsync] + List messages = new LinkedList<>(); + messages.add(Message.of("payload1")); + messages.add(Message.of("payload2")); + Future> future = topic.publishAsync(messages); + // ... + List messageIds = future.get(); + // [END publishMessageListAsync] + return messageIds; + } + + /** + * Example of publishing some messages to the topic. + */ + // [TARGET publish(Message, Message...)] + public List publishMessages() { + // [START publishMessages] + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = topic.publish(message1, message2); + // [END publishMessages] + return messageIds; + } + + /** + * Example of asynchronously publishing some messages to the topic. + */ + // [TARGET publishAsync(Message, Message...)] + public List publishMessagesAsync() + throws ExecutionException, InterruptedException { + // [START publishMessagesAsync] + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Future> future = topic.publishAsync(message1, message2); + // ... + List messageIds = future.get(); + // [END publishMessagesAsync] + return messageIds; + } + + /** + * Example of listing subscriptions for the topic, specifying the page size. + */ + // [TARGET listSubscriptions(ListOption...)] + public Page listSubscriptionsForTopic() { + // [START listSubscriptionsForTopic] + Page subscriptions = topic.listSubscriptions(ListOption.pageSize(100)); + Iterator subscriptionIterator = subscriptions.iterateAll(); + while (subscriptionIterator.hasNext()) { + SubscriptionId subscription = subscriptionIterator.next(); + // do something with the subscription identity + } + // [END listSubscriptionsForTopic] + return subscriptions; + } + + /** + * Example of asynchronously listing subscriptions for the topic, specifying the page size. + */ + // [TARGET listSubscriptionsAsync(ListOption...)] + public Page listSubscriptionsForTopicAsync() + throws ExecutionException, InterruptedException { + // [START listSubscriptionsForTopicAsync] + Future> future = + topic.listSubscriptionsAsync(ListOption.pageSize(100)); + // ... + AsyncPage subscriptions = future.get(); + Iterator subscriptionIterator = subscriptions.iterateAll(); + while (subscriptionIterator.hasNext()) { + SubscriptionId subscription = subscriptionIterator.next(); + // do something with the subscription identity + } + // [END listSubscriptionsForTopicAsync] + return subscriptions; + } + + /** + * Example of getting the topic's policy. + */ + // [TARGET getPolicy()] + public Policy getPolicy() { + // [START getPolicy] + Policy policy = topic.getPolicy(); + if (policy == null) { + // topic was not found + } + // [END getPolicy] + return policy; + } + + /** + * Example of asynchronously getting the topic's policy. + */ + // [TARGET getPolicyAsync()] + public Policy getPolicyAsync() throws ExecutionException, InterruptedException { + // [START getPolicyAsync] + Future future = topic.getPolicyAsync(); + // ... + Policy policy = future.get(); + if (policy == null) { + // topic was not found + } + // [END getPolicyAsync] + return policy; + } + + /** + * Example of replacing the topic's policy. + */ + // [TARGET replacePolicy(Policy)] + public Policy replacePolicy() { + // [START replacePolicy] + Policy policy = topic.getPolicy(); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + updatedPolicy = topic.replacePolicy(updatedPolicy); + // [END replacePolicy] + return updatedPolicy; + } + + /** + * Example of asynchronously replacing the topic's policy. + */ + // [TARGET replacePolicyAsync(Policy)] + public Policy replacePolicyAsync() + throws ExecutionException, InterruptedException { + // [START replacePolicyAsync] + Policy policy = topic.getPolicy(); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + Future future = topic.replacePolicyAsync(updatedPolicy); + // ... + updatedPolicy = future.get(); + // [END replacePolicyAsync] + return updatedPolicy; + } + + /** + * Example of testing whether the caller has the provided permissions on the topic. + */ + // [TARGET testPermissions(List)] + public List testPermissions() { + // [START testPermissions] + List permissions = new LinkedList<>(); + permissions.add("pubsub.topics.get"); + List testedPermissions = topic.testPermissions(permissions); + // [END testPermissions] + return testedPermissions; + } + + /** + * Example of asynchronously testing whether the caller has the provided permissions on the + * topic. + */ + // [TARGET testPermissionsAsync(List)] + public List testPermissionsAsync() + throws ExecutionException, InterruptedException { + // [START testPermissionsAsync] + List permissions = new LinkedList<>(); + permissions.add("pubsub.topics.get"); + Future> future = topic.testPermissionsAsync(permissions); + // ... + List testedPermissions = future.get(); + // [END testPermissionsAsync] + return testedPermissions; + } +} diff --git a/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITTopicSnippets.java b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITTopicSnippets.java new file mode 100644 index 000000000000..4a091c45248c --- /dev/null +++ b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITTopicSnippets.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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.cloud.examples.pubsub.snippets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.Identity; +import com.google.cloud.Page; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.SubscriptionId; +import com.google.cloud.pubsub.SubscriptionInfo; +import com.google.cloud.pubsub.Topic; +import com.google.cloud.pubsub.TopicInfo; +import com.google.common.collect.Iterators; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ITTopicSnippets { + + private static final String TOPIC = "it-topic-snippets-topic-" + UUID.randomUUID().toString(); + private static final String SUBSCRIPTION = + "it-topic-snippets-subscription-" + UUID.randomUUID().toString(); + + private static PubSub pubsub; + private static Topic topic; + + @BeforeClass + public static void beforeClass() { + pubsub = PubSubOptions.defaultInstance().service(); + topic = pubsub.create(TopicInfo.of(TOPIC)); + } + + @AfterClass + public static void afterClass() throws Exception { + if (pubsub != null) { + topic.delete(); + pubsub.close(); + } + } + + @Test + public void testTopic() throws ExecutionException, InterruptedException { + TopicSnippets topicSnippets = new TopicSnippets(topic); + Topic updatedTopic = topicSnippets.reload(); + assertEquals(topic, updatedTopic); + updatedTopic = topicSnippets.reloadAsync(); + assertEquals(topic, updatedTopic); + assertNotNull(topicSnippets.publishOneMessage()); + assertNotNull(topicSnippets.publishOneMessageAsync()); + assertEquals(2, topicSnippets.publishMessageList().size()); + assertEquals(2, topicSnippets.publishMessageListAsync().size()); + assertEquals(2, topicSnippets.publishMessages().size()); + assertEquals(2, topicSnippets.publishMessagesAsync().size()); + } + + @Test + public void testTopicSubscriptions() throws ExecutionException, InterruptedException { + TopicSnippets topicSnippets = new TopicSnippets(topic); + pubsub.create(SubscriptionInfo.of(TOPIC, SUBSCRIPTION)); + try { + Page subscriptions = topicSnippets.listSubscriptionsForTopic(); + while (Iterators.size(subscriptions.values().iterator()) < 1) { + subscriptions = topicSnippets.listSubscriptionsForTopic(); + } + assertEquals(SUBSCRIPTION, subscriptions.values().iterator().next().subscription()); + subscriptions = topicSnippets.listSubscriptionsForTopicAsync(); + while (Iterators.size(subscriptions.values().iterator()) < 1) { + subscriptions = topicSnippets.listSubscriptionsForTopic(); + } + assertEquals(SUBSCRIPTION, subscriptions.values().iterator().next().subscription()); + } finally { + pubsub.deleteSubscription(SUBSCRIPTION); + } + } + + @Test + public void testPolicy() throws ExecutionException, InterruptedException { + TopicSnippets topicSnippets = new TopicSnippets(topic); + Policy policy = topicSnippets.getPolicy(); + assertNotNull(policy); + assertEquals(policy, topicSnippets.getPolicyAsync()); + policy = topicSnippets.replacePolicy(); + assertTrue(policy.bindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + policy = topic.replacePolicy(policy.toBuilder() + .removeIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()); + assertFalse(policy.bindings().containsKey(Role.viewer())); + policy = topicSnippets.replacePolicyAsync(); + assertTrue(policy.bindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + assertTrue(topicSnippets.delete()); + assertFalse(topicSnippets.deleteAsync()); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java index e0c3cbf30f0f..d4b9e9069636 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java @@ -109,6 +109,16 @@ public PubSub pubSub() { /** * Deletes this topic. * + *

Example of deleting the topic. + *

 {@code
+   * boolean deleted = topic.delete();
+   * if (deleted) {
+   *   // the topic was deleted
+   * } else {
+   *   // the topic was not found
+   * }
+   * }
+ * * @return {@code true} if the topic was deleted, {@code false} if it was not found * @throws PubSubException upon failure */ @@ -121,6 +131,18 @@ public boolean delete() { * the result. {@link Future#get()} returns {@code true} if the topic was deleted, {@code false} * if it was not found. * + *

Example of asynchronously deleting the topic. + *

 {@code
+   * Future future = topic.deleteAsync();
+   * // ...
+   * boolean deleted = future.get();
+   * if (deleted) {
+   *   // the topic was deleted
+   * } else {
+   *   // the topic was not found
+   * }
+   * }
+ * * @throws PubSubException upon failure */ public Future deleteAsync() { @@ -130,6 +152,14 @@ public Future deleteAsync() { /** * Fetches current topic's latest information. Returns {@code null} if the topic does not exist. * + *

Example of getting the topic's latest information. + *

 {@code
+   * Topic latestTopic = topic.reload();
+   * if (latestTopic == null) {
+   *   // the topic was not found
+   * }
+   * }
+ * * @return a {@code Topic} object with latest information or {@code null} if not found * @throws PubSubException upon failure */ @@ -142,6 +172,16 @@ public Topic reload() { * {@code Future} object to consume the result. {@link Future#get()} returns a {@code Topic} * object with latest information or {@code null} if not found. * + *

Example of asynchronously getting the topic's latest information. + *

 {@code
+   * Future future = topic.reloadAsync();
+   * // ...
+   * Topic latestTopic = future.get();
+   * if (latestTopic == null) {
+   *   // the topic was not found
+   * }
+   * }
+ * * @throws PubSubException upon failure */ public Future reloadAsync() { @@ -152,6 +192,12 @@ public Future reloadAsync() { * Publishes a message to this topic. This method returns a service-generated id for the published * message. Service-generated ids are guaranteed to be unique within the topic. * + *

Example of publishing one message to the topic. + *

 {@code
+   * Message message = Message.of("payload");
+   * String messageId = topic.publish(message);
+   * }
+ * * @param message the message to publish * @return a unique service-generated id for the message * @throws PubSubException upon failure, if the topic does not exist or if the message has empty @@ -167,6 +213,14 @@ public String publish(Message message) { * id for the published message. Service-generated ids are guaranteed to be unique within the * topic. * + *

Example of asynchronously publishing one message to the topic. + *

 {@code
+   * Message message = Message.of("payload");
+   * Future future = topic.publishAsync(message);
+   * // ...
+   * String messageId = future.get();
+   * }
+ * * @param message the message to publish * @return a {@code Future} for the unique service-generated id for the message */ @@ -179,6 +233,13 @@ public Future publishAsync(Message message) { * ids for the published messages. Service-generated ids are guaranteed to be unique within the * topic. * + *

Example of publishing some messages to the topic. + *

 {@code
+   * Message message1 = Message.of("payload1");
+   * Message message2 = Message.of("payload2");
+   * List messageIds = topic.publish(message1, message2);
+   * }
+ * * @param message the first message to publish * @param messages other messages to publish * @return a list of unique, service-generated, ids. Ids are in the same order as the messages. @@ -195,6 +256,15 @@ public List publish(Message message, Message... messages) { * service-generated ids for the published messages. Service-generated ids are guaranteed to be * unique within the topic. * + *

Example of asynchronously publishing some messages to the topic. + *

 {@code
+   * Message message1 = Message.of("payload1");
+   * Message message2 = Message.of("payload2");
+   * Future> future = topic.publishAsync(message1, message2);
+   * // ...
+   * List messageIds = future.get();
+   * }
+ * * @param message the first message to publish * @param messages other messages to publish * @return a {@code Future} for the unique, service-generated ids. Ids are in the same order as @@ -209,6 +279,14 @@ public Future> publishAsync(Message message, Message... messages) { * ids for the published messages. Service-generated ids are guaranteed to be unique within the * topic. * + *

Example of publishing a list of messages to the topic. + *

 {@code
+   * List messages = new LinkedList<>();
+   * messages.add(Message.of("payload1"));
+   * messages.add(Message.of("payload2"));
+   * List messageIds = topic.publish(messages);
+   * }
+ * * @param messages the messages to publish * @return a list of unique, service-generated, ids. Ids are in the same order as the messages. * @throws PubSubException upon failure, if the topic does not exist or if one of the messages has @@ -224,6 +302,16 @@ public List publish(Iterable messages) { * service-generated ids for the published messages. Service-generated ids are guaranteed to be * unique within the topic. * + *

Example of asynchronously publishing a list of messages to the topic. + *

 {@code
+   * List messages = new LinkedList<>();
+   * messages.add(Message.of("payload1"));
+   * messages.add(Message.of("payload2"));
+   * Future> future = topic.publishAsync(messages);
+   * // ...
+   * List messageIds = future.get();
+   * }
+ * * @param messages the messages to publish * @return a {@code Future} for the unique, service-generated ids. Ids are in the same order as * the messages. @@ -237,6 +325,16 @@ public Future> publishAsync(Iterable messages) { * object that can be used to consume paginated results. Use {@link ListOption} to specify the * page size or the page token from which to start listing subscriptions. * + *

Example of listing subscriptions for the topic, specifying the page size. + *

 {@code
+   * Page subscriptions = topic.listSubscriptions(ListOption.pageSize(100));
+   * Iterator subscriptionIterator = subscriptions.iterateAll();
+   * while (subscriptionIterator.hasNext()) {
+   *   SubscriptionId subscription = subscriptionIterator.next();
+   *   // do something with the subscription identity
+   * }
+   * }
+ * * @throws PubSubException upon failure */ public Page listSubscriptions(ListOption... options) { @@ -249,6 +347,20 @@ public Page listSubscriptions(ListOption... options) { * {@link AsyncPage} object that can be used to asynchronously handle paginated results. Use * {@link ListOption} to specify the page size or the page token from which to start listing * subscriptions. + * + *

Example of asynchronously listing subscriptions for the topic, specifying the page size. + *

 {@code
+   * Future> future =
+   *     topic.listSubscriptionsAsync(ListOption.pageSize(100));
+   * // ...
+   * AsyncPage subscriptions = future.get();
+   * Iterator subscriptionIterator = subscriptions.iterateAll();
+   * while (subscriptionIterator.hasNext()) {
+   *   SubscriptionId subscription = subscriptionIterator.next();
+   *   // do something with the subscription identity
+   * }
+   * }
+ * */ public Future> listSubscriptionsAsync(ListOption... options) { return pubsub.listSubscriptionsAsync(name(), options); @@ -258,6 +370,14 @@ public Future> listSubscriptionsAsync(ListOption... op * Returns the IAM access control policy for this topic. Returns {@code null} if the topic was not * found. * + *

Example of getting the topic's policy. + *

 {@code
+   * Policy policy = topic.getPolicy();
+   * if (policy == null) {
+   *   // topic was not found
+   * }
+   * }
+ * * @throws PubSubException upon failure */ public Policy getPolicy() { @@ -269,6 +389,16 @@ public Policy getPolicy() { * {@code Future} object to consume the result. {@link Future#get()} returns the requested policy * or {@code null} if the topic was not found. * + *

Example of asynchronously getting the topic's policy. + *

 {@code
+   * Future future = topic.getPolicyAsync();
+   * // ...
+   * Policy policy = future.get();
+   * if (policy == null) {
+   *   // topic was not found
+   * }
+   * }
+ * * @throws PubSubException upon failure */ public Future getPolicyAsync() { @@ -289,6 +419,15 @@ public Future getPolicyAsync() { * {@code PubSubException} is thrown, denoting that the server aborted update. If an etag is not * provided, the policy is overwritten blindly. * + *

Example of replacing the topic's policy. + *

 {@code
+   * Policy policy = topic.getPolicy();
+   * Policy updatedPolicy = policy.toBuilder()
+   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
+   *     .build();
+   * updatedPolicy = topic.replacePolicy(updatedPolicy);
+   * }
+ * * @throws PubSubException upon failure */ public Policy replacePolicy(Policy newPolicy) { @@ -311,6 +450,17 @@ public Policy replacePolicy(Policy newPolicy) { * {@code PubSubException}, denoting that the server aborted update. If an etag is not provided, * the policy is overwritten blindly. * + *

Example of asynchronously replacing the topic's policy. + *

 {@code
+   * Policy policy = topic.getPolicy();
+   * Policy updatedPolicy = policy.toBuilder()
+   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
+   *     .build();
+   * Future future = topic.replacePolicyAsync(updatedPolicy);
+   * // ...
+   * updatedPolicy = future.get();
+   * }
+ * * @throws PubSubException upon failure */ public Future replacePolicyAsync(Policy newPolicy) { @@ -325,6 +475,13 @@ public Future replacePolicyAsync(Policy newPolicy) { * such as a customized graphical user interface. For example, the Cloud Platform Console tests * IAM permissions internally to determine which UI should be available to the logged-in user. * + *

Example of testing whether the caller has the provided permissions on the topic. + *

 {@code
+   * List permissions = new LinkedList<>();
+   * permissions.add("pubsub.topics.get");
+   * List testedPermissions = topic.testPermissions(permissions);
+   * }
+ * * @return A list of booleans representing whether the caller has the permissions specified (in * the order of the given permissions) * @throws PubSubException upon failure @@ -343,6 +500,16 @@ public List testPermissions(List permissions) { * such as a customized graphical user interface. For example, the Cloud Platform Console tests * IAM permissions internally to determine which UI should be available to the logged-in user. * + *

Example of asynchronously testing whether the caller has the provided permissions on the + * topic. + *

 {@code
+   * List permissions = new LinkedList<>();
+   * permissions.add("pubsub.topics.get");
+   * Future> future = topic.testPermissionsAsync(permissions);
+   * // ...
+   * List testedPermissions = future.get();
+   * }
+ * * @return A {@code Future} object to consume the result. {@link Future#get()} returns a list of * booleans representing whether the caller has the permissions specified (in the order of the * given permissions)