From a381426fa596b4999e3092f506b974b7c28dacae Mon Sep 17 00:00:00 2001 From: Michael Darakananda Date: Tue, 7 Feb 2017 13:34:12 +1100 Subject: [PATCH] merge master into pubsub-hp (#1591) * Update version to 0.8.1-SNAPSHOT (#1467) Also, update versions in README to 0.8.0 * Add link to Maven Central for maven-central badge. (#1468) Used to link to the image, which wasn't super useful. * fix more races in pubsub tests Previously BlockingProcessStreamReader has a terminate() method, used to tell the Reader to stop reading from the emulator process. This causes an inter-process race. If the Reader stops before reading emulator's output, the emulator process will hang as it tries to write to stdout/stderr as there's no one to read from the other side of the pipe. Since there is no way to safely stop the Reader, this commit deletes the method and its associated test. Additionally, the timeout for LocalSystemTest is increased to 3 minutes, since the emulator, somehow, consistently takes just longer than a minute to shut down. * Regenerating SPI layer (#1501) * Converting Error Reporting and Monitoring to use resource name types * Removing formatX/parseX methods from pubsub, converting usage of the same to resource name types * New methods in Logging and PubSub * Updating grpc dependency to 1.0.3 (#1504) * Release 0.8.1 (#1512) * Fix code snippet (wrong method name) in README.md Original code snippet in _"Querying data"_ section: `..while (!queryResponse.jobComplete()) {..` This results in a compile error: _"Cannot resolve method jobComplete()"_ The correct method is `jobCompleted()` * Updating version in README files. [ci skip] * Update version to 0.8.2-alpha-SNAPSHOT * Allow path in URIs passed to newFileSystem (#1470) * This makes it easier for users who start with a URI describing a full path to get a FileSystem that can work with that path, since they no longer have to needlessly remove the path from the URI. Note that Oracle's description of newFileSystem [1] puts no restriction on the passed URI. [1] https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystems.html#newFileSystem(java.net.URI,%20java.util.Map) * Preventing logging re-entrance at FINE level (#1523) * Preventing logging re-entrance at FINE level Also: * Reducing the scope of synchronized blocks * Removing logger exclusions except for Http2FrameLogger * Add a PathMatcher for CloudStorageFileSystem (#1469) Add a test, as well. We reuse the default PathMatcher since it does a fine job of globbing files. * Set timestamp from LogRecord (#1533) * Initialize the default MonitoredResource from a GAE environment (#1535) * BigQuery: Add support to FormatOptions for AVRO https://github.com/GoogleCloudPlatform/google-cloud-java/issues/1441 Added new constant in FormatOptions, and a corresponding factory method. Updated test cases. Confirmed that AVRO does not require special treatment (like CSV does), so no additional changes are required. * Reverting changed commited by mistake before review. * BigQuery: Add support to FormatOptions for AVRO https://github.com/GoogleCloudPlatform/google-cloud-java/issues/1441 Added new constant in FormatOptions and a corresponding factory method. Updated test cases. Confirmed that AVRO does not require special treatment (like CSV does), so no additional changes are required. * use RpcFuture and remove old BundlingSettings (#1572) * use RpcFuture and remove old BundlingSettings * Release 0.8.2 Note: This version was accidentally released to Sonatype because of experimenting with deployment commands, combined with the fact that autoReleaseAfterClose is set to true. Since releases can't be taken back, we might as well own up to the release and push the code forward. * Updating version in README files. [ci skip] * Fixing javadoc error in GaeFlexLoggingEnhancer (#1582) * get tests to compile and pass --- README.md | 6 +- google-cloud-bigquery/README.md | 8 +- google-cloud-bigquery/pom.xml | 2 +- .../google/cloud/bigquery/FormatOptions.java | 8 + .../cloud/bigquery/FormatOptionsTest.java | 6 + google-cloud-compute/README.md | 6 +- google-cloud-compute/pom.xml | 2 +- google-cloud-contrib/README.md | 6 +- .../google-cloud-nio-examples/README.md | 4 +- .../google-cloud-nio-examples/pom.xml | 2 +- .../google-cloud-nio/README.md | 6 +- google-cloud-contrib/google-cloud-nio/pom.xml | 2 +- .../contrib/nio/CloudStorageFileSystem.java | 7 +- .../nio/CloudStorageFileSystemProvider.java | 10 +- .../CloudStorageFileSystemProviderTest.java | 8 + .../nio/CloudStorageFileSystemTest.java | 24 + google-cloud-contrib/pom.xml | 2 +- google-cloud-core/README.md | 6 +- google-cloud-core/pom.xml | 8 +- google-cloud-datastore/README.md | 6 +- google-cloud-datastore/pom.xml | 2 +- google-cloud-dns/README.md | 6 +- google-cloud-dns/pom.xml | 2 +- google-cloud-errorreporting/pom.xml | 4 +- .../spi/v1beta1/ErrorGroupServiceClient.java | 45 +- .../spi/v1beta1/ErrorStatsServiceClient.java | 92 +- .../v1beta1/ReportErrorsServiceClient.java | 40 +- .../spi/v1beta1/package-info.java | 12 +- .../spi/v1beta1/ErrorGroupServiceTest.java | 21 +- .../spi/v1beta1/ErrorStatsServiceTest.java | 32 +- .../spi/v1beta1/ReportErrorsServiceTest.java | 11 +- google-cloud-examples/README.md | 6 +- google-cloud-examples/pom.xml | 2 +- .../cloud/examples/pubsub/PubSubExample.java | 898 ++++++++ .../pubsub/snippets/PubSubSnippets.java | 1050 +++++++++ .../pubsub/snippets/SubscriptionSnippets.java | 316 +++ .../pubsub/snippets/TopicSnippets.java | 332 +++ .../pubsub/snippets/ITPubSubSnippets.java | 269 +++ .../snippets/ITSubscriptionSnippets.java | 125 ++ .../pubsub/snippets/ITTopicSnippets.java | 118 + google-cloud-language/pom.xml | 4 +- google-cloud-logging/README.md | 6 +- google-cloud-logging/pom.xml | 4 +- .../cloud/logging/GaeFlexLoggingEnhancer.java | 88 + .../google/cloud/logging/LoggingHandler.java | 189 +- .../com/google/cloud/logging/LoggingImpl.java | 4 - .../cloud/logging/spi/DefaultLoggingRpc.java | 58 +- .../logging/spi/v2/ConfigServiceV2Client.java | 62 +- .../spi/v2/LoggingServiceV2Client.java | 149 +- .../spi/v2/LoggingServiceV2Settings.java | 177 +- .../logging/spi/v2/PagedResponseWrappers.java | 14 + .../logging/AsyncLoggingHandlerTest.java | 5 +- .../cloud/logging/LoggingHandlerTest.java | 234 +- .../logging/spi/v2/LoggingServiceV2Test.java | 49 + .../spi/v2/MockLoggingServiceV2Impl.java | 16 + google-cloud-monitoring/pom.xml | 4 +- .../monitoring/spi/v3/GroupServiceClient.java | 128 +- .../spi/v3/MetricServiceClient.java | 247 +- .../cloud/monitoring/spi/v3/package-info.java | 8 +- .../monitoring/spi/v3/GroupServiceTest.java | 66 +- .../monitoring/spi/v3/MetricServiceTest.java | 111 +- google-cloud-pubsub/README.md | 6 +- google-cloud-pubsub/pom.xml | 2 +- .../cloud/pubsub/AckDeadlineRenewer.java | 317 +++ .../java/com/google/cloud/pubsub/Message.java | 454 ++++ .../cloud/pubsub/MessageConsumerImpl.java | 302 +++ .../java/com/google/cloud/pubsub/Option.java | 77 + .../google/cloud/pubsub/PolicyMarshaller.java | 36 + .../java/com/google/cloud/pubsub/PubSub.java | 1446 ++++++++++++ .../google/cloud/pubsub/PubSubException.java | 46 + .../google/cloud/pubsub/PubSubFactory.java | 24 + .../com/google/cloud/pubsub/PubSubImpl.java | 764 +++++++ .../google/cloud/pubsub/PubSubOptions.java | 144 ++ .../com/google/cloud/pubsub/PushConfig.java | 363 +++ .../google/cloud/pubsub/ReceivedMessage.java | 297 +++ .../com/google/cloud/pubsub/Subscription.java | 611 +++++ .../google/cloud/pubsub/SubscriptionId.java | 104 + .../google/cloud/pubsub/SubscriptionInfo.java | 556 +++++ .../java/com/google/cloud/pubsub/Topic.java | 556 +++++ .../java/com/google/cloud/pubsub/TopicId.java | 154 ++ .../com/google/cloud/pubsub/TopicInfo.java | 201 ++ .../com/google/cloud/pubsub/package-info.java | 54 + .../cloud/pubsub/spi/DefaultPubSubRpc.java | 309 +++ .../google/cloud/pubsub/spi/PubSubRpc.java | 228 ++ .../cloud/pubsub/spi/PubSubRpcFactory.java | 27 + .../pubsub/spi/v1/MessageDispatcher.java | 2 +- .../spi/v1/PollingSubscriberConnection.java | 2 +- .../google/cloud/pubsub/spi/v1/Publisher.java | 22 +- .../cloud/pubsub/spi/v1/PublisherClient.java | 5 +- .../pubsub/spi/v1/PublisherSettings.java | 5 +- .../spi/v1/StreamingSubscriberConnection.java | 2 +- .../cloud/pubsub/spi/v1/Subscriber.java | 11 +- .../cloud/pubsub/spi/v1/SubscriberClient.java | 6 +- .../pubsub/testing/LocalPubSubHelper.java | 23 +- .../cloud/pubsub/AckDeadlineRenewerTest.java | 313 +++ .../google/cloud/pubsub/BaseSystemTest.java | 837 +++++++ .../google/cloud/pubsub/LocalSystemTest.java | 55 + .../cloud/pubsub/MessageConsumerImplTest.java | 453 ++++ .../com/google/cloud/pubsub/MessageTest.java | 176 ++ .../com/google/cloud/pubsub/OptionTest.java | 66 + .../google/cloud/pubsub/PubSubImplTest.java | 1986 +++++++++++++++++ .../com/google/cloud/pubsub/PubSubTest.java | 59 + .../google/cloud/pubsub/PushConfigTest.java | 114 + .../cloud/pubsub/ReceivedMessageTest.java | 223 ++ .../cloud/pubsub/SerializationTest.java | 102 + .../cloud/pubsub/SubscriptionIdTest.java | 56 + .../cloud/pubsub/SubscriptionInfoTest.java | 162 ++ .../google/cloud/pubsub/SubscriptionTest.java | 436 ++++ .../com/google/cloud/pubsub/TopicIdTest.java | 86 + .../google/cloud/pubsub/TopicInfoTest.java | 70 + .../com/google/cloud/pubsub/TopicTest.java | 416 ++++ .../google/cloud/pubsub/it/ITPubSubTest.java | 144 ++ .../google/cloud/pubsub/spi/v1/FakeClock.java | 8 +- .../pubsub/spi/v1/PublisherImplTest.java | 41 +- .../pubsub/testing/LocalPubSubHelperTest.java | 19 + google-cloud-resourcemanager/README.md | 6 +- google-cloud-resourcemanager/pom.xml | 2 +- google-cloud-speech/pom.xml | 4 +- google-cloud-storage/README.md | 6 +- google-cloud-storage/pom.xml | 2 +- google-cloud-trace/pom.xml | 4 +- google-cloud-translate/README.md | 6 +- google-cloud-translate/pom.xml | 2 +- google-cloud-vision/pom.xml | 4 +- google-cloud/README.md | 6 +- google-cloud/pom.xml | 2 +- pom.xml | 8 +- utilities/verify.sh | 4 +- 128 files changed, 17309 insertions(+), 860 deletions(-) create mode 100644 google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/PubSubExample.java create mode 100644 google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/PubSubSnippets.java create mode 100644 google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/SubscriptionSnippets.java create mode 100644 google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/TopicSnippets.java create mode 100644 google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITPubSubSnippets.java create mode 100644 google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITSubscriptionSnippets.java create mode 100644 google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITTopicSnippets.java create mode 100644 google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/AckDeadlineRenewer.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Message.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/MessageConsumerImpl.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Option.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PolicyMarshaller.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSub.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubException.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubFactory.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubImpl.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubOptions.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PushConfig.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/ReceivedMessage.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Subscription.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionId.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionInfo.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicId.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java create mode 100644 google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/AckDeadlineRenewerTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/BaseSystemTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/LocalSystemTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageConsumerImplTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/OptionTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubImplTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PushConfigTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/ReceivedMessageTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SerializationTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionIdTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionInfoTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicIdTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicInfoTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicTest.java create mode 100644 google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/it/ITPubSubTest.java diff --git a/README.md b/README.md index 6d423b25be1f..2c9168c5ba14 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud:0.8.0' +compile 'com.google.cloud:google-cloud:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud" % "0.8.2-alpha" ``` Example Applications diff --git a/google-cloud-bigquery/README.md b/google-cloud-bigquery/README.md index 616494887e3b..ccf9f32d1285 100644 --- a/google-cloud-bigquery/README.md +++ b/google-cloud-bigquery/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-bigquery - 0.8.0-beta + 0.8.2-beta ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-bigquery:0.8.0-beta' +compile 'com.google.cloud:google-cloud-bigquery:0.8.2-beta' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-bigquery" % "0.8.0-beta" +libraryDependencies += "com.google.cloud" % "google-cloud-bigquery" % "0.8.2-beta" ``` Example Application @@ -189,7 +189,7 @@ QueryRequest queryRequest = .build(); // Request query to be executed and wait for results QueryResponse queryResponse = bigquery.query(queryRequest); -while (!queryResponse.jobComplete()) { +while (!queryResponse.jobCompleted()) { Thread.sleep(1000L); queryResponse = bigquery.getQueryResults(queryResponse.getJobId()); } diff --git a/google-cloud-bigquery/pom.xml b/google-cloud-bigquery/pom.xml index 73383afce15f..b433bc49fe16 100644 --- a/google-cloud-bigquery/pom.xml +++ b/google-cloud-bigquery/pom.xml @@ -12,7 +12,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-bigquery diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FormatOptions.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FormatOptions.java index ef451e844a3e..40ec11ebf6b9 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FormatOptions.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FormatOptions.java @@ -32,6 +32,7 @@ public class FormatOptions implements Serializable { static final String CSV = "CSV"; static final String JSON = "NEWLINE_DELIMITED_JSON"; static final String DATASTORE_BACKUP = "DATASTORE_BACKUP"; + static final String AVRO = "AVRO"; private static final long serialVersionUID = -443376052020423691L; private final String type; @@ -94,6 +95,13 @@ public static FormatOptions datastoreBackup() { return new FormatOptions(DATASTORE_BACKUP); } + /** + * Default options for AVRO format. + */ + public static FormatOptions avro() { + return new FormatOptions(AVRO); + } + /** * Default options for the provided format. */ diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FormatOptionsTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FormatOptionsTest.java index bd231f0249e6..a019e347ba84 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FormatOptionsTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FormatOptionsTest.java @@ -30,9 +30,12 @@ public void testConstructor() { assertEquals(FormatOptions.JSON, options.getType()); options = new FormatOptions(FormatOptions.DATASTORE_BACKUP); assertEquals(FormatOptions.DATASTORE_BACKUP, options.getType()); + options = new FormatOptions(FormatOptions.AVRO); + assertEquals(FormatOptions.AVRO, options.getType()); } @Test + @SuppressWarnings("deprecation") public void testConstructorDeprecated() { FormatOptions options = new FormatOptions(FormatOptions.CSV); assertEquals(FormatOptions.CSV, options.type()); @@ -40,6 +43,8 @@ public void testConstructorDeprecated() { assertEquals(FormatOptions.JSON, options.type()); options = new FormatOptions(FormatOptions.DATASTORE_BACKUP); assertEquals(FormatOptions.DATASTORE_BACKUP, options.type()); + options = new FormatOptions(FormatOptions.AVRO); + assertEquals(FormatOptions.AVRO, options.type()); } @Test @@ -47,6 +52,7 @@ public void testFactoryMethods() { assertEquals(FormatOptions.CSV, FormatOptions.csv().getType()); assertEquals(FormatOptions.JSON, FormatOptions.json().getType()); assertEquals(FormatOptions.DATASTORE_BACKUP, FormatOptions.datastoreBackup().getType()); + assertEquals(FormatOptions.AVRO, FormatOptions.avro().getType()); } @Test diff --git a/google-cloud-compute/README.md b/google-cloud-compute/README.md index f7c18a0eafa9..6ebb96c7ff37 100644 --- a/google-cloud-compute/README.md +++ b/google-cloud-compute/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-compute - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-compute:0.8.0' +compile 'com.google.cloud:google-cloud-compute:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-compute" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-compute" % "0.8.2-alpha" ``` Example Application diff --git a/google-cloud-compute/pom.xml b/google-cloud-compute/pom.xml index a9203ad718ac..b4e9ffcc4aad 100644 --- a/google-cloud-compute/pom.xml +++ b/google-cloud-compute/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-compute diff --git a/google-cloud-contrib/README.md b/google-cloud-contrib/README.md index 8b7aa1323089..398caf673cd2 100644 --- a/google-cloud-contrib/README.md +++ b/google-cloud-contrib/README.md @@ -25,16 +25,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-contrib - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-contrib:0.8.0' +compile 'com.google.cloud:google-cloud-contrib:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-contrib" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-contrib" % "0.8.2-alpha" ``` ### google-cloud-nio-examples diff --git a/google-cloud-contrib/google-cloud-nio-examples/README.md b/google-cloud-contrib/google-cloud-nio-examples/README.md index b74d11a79059..bddf1586402d 100644 --- a/google-cloud-contrib/google-cloud-nio-examples/README.md +++ b/google-cloud-contrib/google-cloud-nio-examples/README.md @@ -22,12 +22,12 @@ To run this example: 4. Run the sample with: ``` - java -cp google-cloud-contrib/google-cloud-nio/target/google-cloud-nio-0.7.1-SNAPSHOT-shaded.jar:google-cloud-contrib/google-cloud-nio-examples/target/google-cloud-nio-examples-0.7.1-SNAPSHOT.jar com.google.cloud.nio.examples.ListFilesystems + java -cp google-cloud-contrib/google-cloud-nio/target/google-cloud-nio-0.8.3-alpha-SNAPSHOT-shaded.jar:google-cloud-contrib/google-cloud-nio-examples/target/google-cloud-nio-examples-0.8.3-alpha-SNAPSHOT.jar com.google.cloud.nio.examples.ListFilesystems ``` Notice that it lists Google Cloud Storage, which it wouldn't if you ran it without the NIO jar: ``` - java -cp google-cloud-contrib/google-cloud-nio-examples/target/google-cloud-nio-examples-0.7.1-SNAPSHOT.jar com.google.cloud.nio.examples.ListFilesystems + java -cp google-cloud-contrib/google-cloud-nio-examples/target/google-cloud-nio-examples-0.8.3-alpha-SNAPSHOT.jar com.google.cloud.nio.examples.ListFilesystems ``` The sample doesn't have anything about Google Cloud Storage in it. It gets that ability from the NIO diff --git a/google-cloud-contrib/google-cloud-nio-examples/pom.xml b/google-cloud-contrib/google-cloud-nio-examples/pom.xml index 2b07df873af8..9822164147c2 100644 --- a/google-cloud-contrib/google-cloud-nio-examples/pom.xml +++ b/google-cloud-contrib/google-cloud-nio-examples/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-contrib - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-nio-examples diff --git a/google-cloud-contrib/google-cloud-nio/README.md b/google-cloud-contrib/google-cloud-nio/README.md index 7d7df3b722bc..74ec767491ed 100644 --- a/google-cloud-contrib/google-cloud-nio/README.md +++ b/google-cloud-contrib/google-cloud-nio/README.md @@ -26,16 +26,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-nio - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-nio:0.8.0' +compile 'com.google.cloud:google-cloud-nio:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-nio" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-nio" % "0.8.2-alpha" ``` Example Applications diff --git a/google-cloud-contrib/google-cloud-nio/pom.xml b/google-cloud-contrib/google-cloud-nio/pom.xml index 0646be3fe3da..297938dd5932 100644 --- a/google-cloud-contrib/google-cloud-nio/pom.xml +++ b/google-cloud-contrib/google-cloud-nio/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-contrib - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-nio diff --git a/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystem.java b/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystem.java index 60a39fb5a817..4f031c92f740 100644 --- a/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystem.java +++ b/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystem.java @@ -27,6 +27,7 @@ import java.net.URISyntaxException; import java.nio.file.FileStore; import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.WatchService; @@ -210,13 +211,9 @@ public Set supportedFileAttributeViews() { return SUPPORTED_VIEWS; } - /** - * Throws {@link UnsupportedOperationException} because this feature hasn't been implemented yet. - */ @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { - // TODO(#813): Implement me. - throw new UnsupportedOperationException(); + return FileSystems.getDefault().getPathMatcher(syntaxAndPattern); } /** diff --git a/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java b/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java index 727327d215fd..f65549b69fec 100644 --- a/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java +++ b/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java @@ -170,14 +170,15 @@ public CloudStorageFileSystem getFileSystem(URI uri) { } /** - * Returns Cloud Storage file system, provided a URI with no path, e.g. {@code gs://bucket}. + * Returns Cloud Storage file system, provided a URI, e.g. {@code gs://bucket}. + * The URI can include a path component (that will be ignored). * * @param uri bucket and current working directory, e.g. {@code gs://bucket} * @param env map of configuration options, whose keys correspond to the method names of * {@link CloudStorageConfiguration.Builder}. However you are not allowed to set the working * directory, as that should be provided in the {@code uri} - * @throws IllegalArgumentException if {@code uri} specifies a user, query, fragment, or scheme is - * not {@value CloudStorageFileSystem#URI_SCHEME} + * @throws IllegalArgumentException if {@code uri} specifies a port, user, query, or fragment, or + * if scheme is not {@value CloudStorageFileSystem#URI_SCHEME} */ @Override public CloudStorageFileSystem newFileSystem(URI uri, Map env) { @@ -191,11 +192,10 @@ public CloudStorageFileSystem newFileSystem(URI uri, Map env) { CloudStorageFileSystem.URI_SCHEME, uri); checkArgument( uri.getPort() == -1 - && isNullOrEmpty(uri.getPath()) && isNullOrEmpty(uri.getQuery()) && isNullOrEmpty(uri.getFragment()) && isNullOrEmpty(uri.getUserInfo()), - "GCS FileSystem URIs mustn't have: port, userinfo, path, query, or fragment: %s", + "GCS FileSystem URIs mustn't have: port, userinfo, query, or fragment: %s", uri); CloudStorageUtil.checkBucket(uri.getHost()); initStorage(); diff --git a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java index 86e87d0e71e1..970d60217e57 100644 --- a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java +++ b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java @@ -54,7 +54,9 @@ import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Unit tests for {@link CloudStorageFileSystemProvider}. @@ -644,6 +646,12 @@ public void testProviderEquals() { assertThat(path1.getFileSystem().provider()).isNotEqualTo(path3.getFileSystem().provider()); } + @Test + public void testNewFileSystem() throws IOException { + Map env = new HashMap<>(); + FileSystems.newFileSystem(URI.create("gs://bucket/path/to/file"), env); + } + private static CloudStorageConfiguration permitEmptyPathComponents(boolean value) { return CloudStorageConfiguration.builder().permitEmptyPathComponents(value).build(); } diff --git a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java index ec856447b96a..d7d5b346c376 100644 --- a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java +++ b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java @@ -34,6 +34,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -159,4 +160,27 @@ public void testListFiles() throws IOException { assertThat(got).containsExactlyElementsIn(goodPaths); } } + + @Test + public void testMatcher() throws IOException { + try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) { + String pattern1 = "glob:*.java"; + PathMatcher javaFileMatcher = fs.getPathMatcher(pattern1); + assertMatches(fs, javaFileMatcher, "a.java", true); + assertMatches(fs, javaFileMatcher, "a.text", false); + assertMatches(fs, javaFileMatcher, "folder/c.java", true); + assertMatches(fs, javaFileMatcher, "d", false); + + String pattern2 = "glob:*.{java,text}"; + PathMatcher javaAndTextFileMatcher = fs.getPathMatcher(pattern2); + assertMatches(fs, javaAndTextFileMatcher, "a.java", true); + assertMatches(fs, javaAndTextFileMatcher, "a.text", true); + assertMatches(fs, javaAndTextFileMatcher, "folder/c.java", true); + assertMatches(fs, javaAndTextFileMatcher, "d", false); + } + } + + private void assertMatches(FileSystem fs, PathMatcher matcher, String toMatch, boolean expected) { + assertThat(matcher.matches(fs.getPath(toMatch).getFileName())).isEqualTo(expected); + } } diff --git a/google-cloud-contrib/pom.xml b/google-cloud-contrib/pom.xml index 81f42e03b96d..de5a2dbfdbc2 100644 --- a/google-cloud-contrib/pom.xml +++ b/google-cloud-contrib/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-contrib diff --git a/google-cloud-core/README.md b/google-cloud-core/README.md index f711365e184b..a82cc30ff47f 100644 --- a/google-cloud-core/README.md +++ b/google-cloud-core/README.md @@ -19,16 +19,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-core - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-core:0.8.0' +compile 'com.google.cloud:google-cloud-core:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-core" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-core" % "0.8.2-alpha" ``` Troubleshooting diff --git a/google-cloud-core/pom.xml b/google-cloud-core/pom.xml index d395b23313f9..7ad0a8ae40d6 100644 --- a/google-cloud-core/pom.xml +++ b/google-cloud-core/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-core @@ -111,7 +111,7 @@ com.google.api gax - 0.0.27 + 0.0.28 io.grpc @@ -122,7 +122,7 @@ com.google.api.grpc grpc-google-common-protos - 0.1.3 + 0.1.5 io.grpc @@ -133,7 +133,7 @@ com.google.api.grpc grpc-google-iam-v1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-datastore/README.md b/google-cloud-datastore/README.md index db84b8db9ad1..a313c65fa4ba 100644 --- a/google-cloud-datastore/README.md +++ b/google-cloud-datastore/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-datastore - 0.8.0-beta + 0.8.2-beta ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-datastore:0.8.0-beta' +compile 'com.google.cloud:google-cloud-datastore:0.8.2-beta' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "0.8.0-beta" +libraryDependencies += "com.google.cloud" % "google-cloud-datastore" % "0.8.2-beta" ``` Example Application diff --git a/google-cloud-datastore/pom.xml b/google-cloud-datastore/pom.xml index 100ad5edcfd9..5622bea2c75c 100644 --- a/google-cloud-datastore/pom.xml +++ b/google-cloud-datastore/pom.xml @@ -12,7 +12,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-datastore diff --git a/google-cloud-dns/README.md b/google-cloud-dns/README.md index 6ae180f976dd..ee23f46ed354 100644 --- a/google-cloud-dns/README.md +++ b/google-cloud-dns/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-dns - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-dns:0.8.0' +compile 'com.google.cloud:google-cloud-dns:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-dns" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-dns" % "0.8.2-alpha" ``` Example Application diff --git a/google-cloud-dns/pom.xml b/google-cloud-dns/pom.xml index a53283c9f453..368d39fb466d 100644 --- a/google-cloud-dns/pom.xml +++ b/google-cloud-dns/pom.xml @@ -13,7 +13,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-dns diff --git a/google-cloud-errorreporting/pom.xml b/google-cloud-errorreporting/pom.xml index dcff54850fc9..3e8053a6cc7c 100644 --- a/google-cloud-errorreporting/pom.xml +++ b/google-cloud-errorreporting/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-errorreporting @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-error-reporting-v1beta1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceClient.java b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceClient.java index 48b9cc6bba46..33e1edc3d84a 100644 --- a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceClient.java +++ b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceClient.java @@ -17,9 +17,9 @@ import com.google.api.gax.grpc.ChannelAndExecutor; import com.google.api.gax.grpc.UnaryCallable; -import com.google.api.gax.protobuf.PathTemplate; import com.google.devtools.clouderrorreporting.v1beta1.ErrorGroup; import com.google.devtools.clouderrorreporting.v1beta1.GetGroupRequest; +import com.google.devtools.clouderrorreporting.v1beta1.GroupName; import com.google.devtools.clouderrorreporting.v1beta1.UpdateGroupRequest; import com.google.protobuf.ExperimentalApi; import io.grpc.ManagedChannel; @@ -40,8 +40,8 @@ *
  * 
  * try (ErrorGroupServiceClient errorGroupServiceClient = ErrorGroupServiceClient.create()) {
- *   String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
- *   ErrorGroup response = errorGroupServiceClient.getGroup(formattedGroupName);
+ *   GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]");
+ *   ErrorGroup response = errorGroupServiceClient.getGroup(groupName);
  * }
  * 
  * 
@@ -97,26 +97,6 @@ public class ErrorGroupServiceClient implements AutoCloseable { private final UnaryCallable getGroupCallable; private final UnaryCallable updateGroupCallable; - private static final PathTemplate GROUP_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}/groups/{group}"); - - /** Formats a string containing the fully-qualified path to represent a group resource. */ - public static final String formatGroupName(String project, String group) { - return GROUP_PATH_TEMPLATE.instantiate( - "project", project, - "group", group); - } - - /** Parses the project from the given fully-qualified path which represents a group resource. */ - public static final String parseProjectFromGroupName(String groupName) { - return GROUP_PATH_TEMPLATE.parse(groupName).get("project"); - } - - /** Parses the group from the given fully-qualified path which represents a group resource. */ - public static final String parseGroupFromGroupName(String groupName) { - return GROUP_PATH_TEMPLATE.parse(groupName).get("group"); - } - /** Constructs an instance of ErrorGroupServiceClient with default settings. */ public static final ErrorGroupServiceClient create() throws IOException { return create(ErrorGroupServiceSettings.defaultBuilder().build()); @@ -179,8 +159,8 @@ public final ErrorGroupServiceSettings getSettings() { * *

    * try (ErrorGroupServiceClient errorGroupServiceClient = ErrorGroupServiceClient.create()) {
-   *   String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
-   *   ErrorGroup response = errorGroupServiceClient.getGroup(formattedGroupName);
+   *   GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]");
+   *   ErrorGroup response = errorGroupServiceClient.getGroup(groupName);
    * }
    * 
* @@ -192,9 +172,10 @@ public final ErrorGroupServiceSettings getSettings() { *

Example: <code>projects/my-project-123/groups/my-group</code> * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final ErrorGroup getGroup(String groupName) { - GROUP_PATH_TEMPLATE.validate(groupName, "getGroup"); - GetGroupRequest request = GetGroupRequest.newBuilder().setGroupName(groupName).build(); + public final ErrorGroup getGroup(GroupName groupName) { + + GetGroupRequest request = + GetGroupRequest.newBuilder().setGroupNameWithGroupName(groupName).build(); return getGroup(request); } @@ -206,9 +187,9 @@ public final ErrorGroup getGroup(String groupName) { * *


    * try (ErrorGroupServiceClient errorGroupServiceClient = ErrorGroupServiceClient.create()) {
-   *   String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
+   *   GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]");
    *   GetGroupRequest request = GetGroupRequest.newBuilder()
-   *     .setGroupName(formattedGroupName)
+   *     .setGroupNameWithGroupName(groupName)
    *     .build();
    *   ErrorGroup response = errorGroupServiceClient.getGroup(request);
    * }
@@ -229,9 +210,9 @@ private final ErrorGroup getGroup(GetGroupRequest request) {
    *
    * 

    * try (ErrorGroupServiceClient errorGroupServiceClient = ErrorGroupServiceClient.create()) {
-   *   String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
+   *   GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]");
    *   GetGroupRequest request = GetGroupRequest.newBuilder()
-   *     .setGroupName(formattedGroupName)
+   *     .setGroupNameWithGroupName(groupName)
    *     .build();
    *   ListenableFuture<ErrorGroup> future = errorGroupServiceClient.getGroupCallable().futureCall(request);
    *   // Do something
diff --git a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceClient.java b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceClient.java
index a612ae883eb7..4a53ccaf92bb 100644
--- a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceClient.java
+++ b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceClient.java
@@ -20,13 +20,13 @@
 
 import com.google.api.gax.grpc.ChannelAndExecutor;
 import com.google.api.gax.grpc.UnaryCallable;
-import com.google.api.gax.protobuf.PathTemplate;
 import com.google.devtools.clouderrorreporting.v1beta1.DeleteEventsRequest;
 import com.google.devtools.clouderrorreporting.v1beta1.DeleteEventsResponse;
 import com.google.devtools.clouderrorreporting.v1beta1.ListEventsRequest;
 import com.google.devtools.clouderrorreporting.v1beta1.ListEventsResponse;
 import com.google.devtools.clouderrorreporting.v1beta1.ListGroupStatsRequest;
 import com.google.devtools.clouderrorreporting.v1beta1.ListGroupStatsResponse;
+import com.google.devtools.clouderrorreporting.v1beta1.ProjectName;
 import com.google.devtools.clouderrorreporting.v1beta1.QueryTimeRange;
 import com.google.protobuf.ExperimentalApi;
 import io.grpc.ManagedChannel;
@@ -48,8 +48,8 @@
  * 
  * 
  * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
- *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
- *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(formattedProjectName);
+ *   ProjectName projectName = ProjectName.create("[PROJECT]");
+ *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(projectName);
  * }
  * 
  * 
@@ -109,19 +109,6 @@ public class ErrorStatsServiceClient implements AutoCloseable { private final UnaryCallable listEventsPagedCallable; private final UnaryCallable deleteEventsCallable; - private static final PathTemplate PROJECT_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}"); - - /** Formats a string containing the fully-qualified path to represent a project resource. */ - public static final String formatProjectName(String project) { - return PROJECT_PATH_TEMPLATE.instantiate("project", project); - } - - /** Parses the project from the given fully-qualified path which represents a project resource. */ - public static final String parseProjectFromProjectName(String projectName) { - return PROJECT_PATH_TEMPLATE.parse(projectName).get("project"); - } - /** Constructs an instance of ErrorStatsServiceClient with default settings. */ public static final ErrorStatsServiceClient create() throws IOException { return create(ErrorStatsServiceSettings.defaultBuilder().build()); @@ -192,9 +179,9 @@ public final ErrorStatsServiceSettings getSettings() { * *

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   QueryTimeRange timeRange = QueryTimeRange.newBuilder().build();
-   *   for (ErrorGroupStats element : errorStatsServiceClient.listGroupStats(formattedProjectName, timeRange).iterateAllElements()) {
+   *   for (ErrorGroupStats element : errorStatsServiceClient.listGroupStats(projectName, timeRange).iterateAllElements()) {
    *     // doThingsWith(element);
    *   }
    * }
@@ -205,18 +192,19 @@ public final ErrorStatsServiceSettings getSettings() {
    *     href="https://support.google.com/cloud/answer/6158840">Google Cloud Platform project
    *     ID</a>.
    *     

Example: <code>projects/my-project-123</code>. - * @param timeRange [Required] List data for the given time range. Only - * <code>ErrorGroupStats</code> with a non-zero count in the given time range are - * returned, unless the request contains an explicit group_id list. If a group_id list is - * given, also <code>ErrorGroupStats</code> with zero occurrences are returned. + * @param timeRange [Optional] List data for the given time range. If not set a default time range + * is used. The field time_range_begin in the response will specify the beginning of this time + * range. Only <code>ErrorGroupStats</code> with a non-zero count in the given + * time range are returned, unless the request contains an explicit group_id list. If a + * group_id list is given, also <code>ErrorGroupStats</code> with zero occurrences + * are returned. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final ListGroupStatsPagedResponse listGroupStats( - String projectName, QueryTimeRange timeRange) { - PROJECT_PATH_TEMPLATE.validate(projectName, "listGroupStats"); + ProjectName projectName, QueryTimeRange timeRange) { ListGroupStatsRequest request = ListGroupStatsRequest.newBuilder() - .setProjectName(projectName) + .setProjectNameWithProjectName(projectName) .setTimeRange(timeRange) .build(); return listGroupStats(request); @@ -230,10 +218,10 @@ public final ListGroupStatsPagedResponse listGroupStats( * *


    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   QueryTimeRange timeRange = QueryTimeRange.newBuilder().build();
    *   ListGroupStatsRequest request = ListGroupStatsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setTimeRange(timeRange)
    *     .build();
    *   for (ErrorGroupStats element : errorStatsServiceClient.listGroupStats(request).iterateAllElements()) {
@@ -257,10 +245,10 @@ public final ListGroupStatsPagedResponse listGroupStats(ListGroupStatsRequest re
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   QueryTimeRange timeRange = QueryTimeRange.newBuilder().build();
    *   ListGroupStatsRequest request = ListGroupStatsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setTimeRange(timeRange)
    *     .build();
    *   ListenableFuture<ListGroupStatsPagedResponse> future = errorStatsServiceClient.listGroupStatsPagedCallable().futureCall(request);
@@ -284,10 +272,10 @@ public final ListGroupStatsPagedResponse listGroupStats(ListGroupStatsRequest re
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   QueryTimeRange timeRange = QueryTimeRange.newBuilder().build();
    *   ListGroupStatsRequest request = ListGroupStatsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setTimeRange(timeRange)
    *     .build();
    *   while (true) {
@@ -318,9 +306,9 @@ public final ListGroupStatsPagedResponse listGroupStats(ListGroupStatsRequest re
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   String groupId = "";
-   *   for (ErrorEvent element : errorStatsServiceClient.listEvents(formattedProjectName, groupId).iterateAllElements()) {
+   *   for (ErrorEvent element : errorStatsServiceClient.listEvents(projectName, groupId).iterateAllElements()) {
    *     // doThingsWith(element);
    *   }
    * }
@@ -332,10 +320,12 @@ public final ListGroupStatsPagedResponse listGroupStats(ListGroupStatsRequest re
    * @param groupId [Required] The group for which events shall be returned.
    * @throws com.google.api.gax.grpc.ApiException if the remote call fails
    */
-  public final ListEventsPagedResponse listEvents(String projectName, String groupId) {
-    PROJECT_PATH_TEMPLATE.validate(projectName, "listEvents");
+  public final ListEventsPagedResponse listEvents(ProjectName projectName, String groupId) {
     ListEventsRequest request =
-        ListEventsRequest.newBuilder().setProjectName(projectName).setGroupId(groupId).build();
+        ListEventsRequest.newBuilder()
+            .setProjectNameWithProjectName(projectName)
+            .setGroupId(groupId)
+            .build();
     return listEvents(request);
   }
 
@@ -347,10 +337,10 @@ public final ListEventsPagedResponse listEvents(String projectName, String group
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   String groupId = "";
    *   ListEventsRequest request = ListEventsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setGroupId(groupId)
    *     .build();
    *   for (ErrorEvent element : errorStatsServiceClient.listEvents(request).iterateAllElements()) {
@@ -374,10 +364,10 @@ public final ListEventsPagedResponse listEvents(ListEventsRequest request) {
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   String groupId = "";
    *   ListEventsRequest request = ListEventsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setGroupId(groupId)
    *     .build();
    *   ListenableFuture<ListEventsPagedResponse> future = errorStatsServiceClient.listEventsPagedCallable().futureCall(request);
@@ -400,10 +390,10 @@ public final UnaryCallable listEvent
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   String groupId = "";
    *   ListEventsRequest request = ListEventsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setGroupId(groupId)
    *     .build();
    *   while (true) {
@@ -433,8 +423,8 @@ public final UnaryCallable listEventsCall
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
-   *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(formattedProjectName);
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
+   *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(projectName);
    * }
    * 
* @@ -443,10 +433,10 @@ public final UnaryCallable listEventsCall * ID](https://support.google.com/cloud/answer/6158840). Example: `projects/my-project-123`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final DeleteEventsResponse deleteEvents(String projectName) { - PROJECT_PATH_TEMPLATE.validate(projectName, "deleteEvents"); + public final DeleteEventsResponse deleteEvents(ProjectName projectName) { + DeleteEventsRequest request = - DeleteEventsRequest.newBuilder().setProjectName(projectName).build(); + DeleteEventsRequest.newBuilder().setProjectNameWithProjectName(projectName).build(); return deleteEvents(request); } @@ -458,9 +448,9 @@ public final DeleteEventsResponse deleteEvents(String projectName) { * *

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   DeleteEventsRequest request = DeleteEventsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .build();
    *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(request);
    * }
@@ -481,9 +471,9 @@ private final DeleteEventsResponse deleteEvents(DeleteEventsRequest request) {
    *
    * 

    * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
-   *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   DeleteEventsRequest request = DeleteEventsRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .build();
    *   ListenableFuture<DeleteEventsResponse> future = errorStatsServiceClient.deleteEventsCallable().futureCall(request);
    *   // Do something
diff --git a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceClient.java b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceClient.java
index 65a6ef234b4e..8189c716e86c 100644
--- a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceClient.java
+++ b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceClient.java
@@ -17,7 +17,7 @@
 
 import com.google.api.gax.grpc.ChannelAndExecutor;
 import com.google.api.gax.grpc.UnaryCallable;
-import com.google.api.gax.protobuf.PathTemplate;
+import com.google.devtools.clouderrorreporting.v1beta1.ProjectName;
 import com.google.devtools.clouderrorreporting.v1beta1.ReportErrorEventRequest;
 import com.google.devtools.clouderrorreporting.v1beta1.ReportErrorEventResponse;
 import com.google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent;
@@ -40,9 +40,9 @@
  * 
  * 
  * try (ReportErrorsServiceClient reportErrorsServiceClient = ReportErrorsServiceClient.create()) {
- *   String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]");
+ *   ProjectName projectName = ProjectName.create("[PROJECT]");
  *   ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build();
- *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(formattedProjectName, event);
+ *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(projectName, event);
  * }
  * 
  * 
@@ -98,19 +98,6 @@ public class ReportErrorsServiceClient implements AutoCloseable { private final UnaryCallable reportErrorEventCallable; - private static final PathTemplate PROJECT_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}"); - - /** Formats a string containing the fully-qualified path to represent a project resource. */ - public static final String formatProjectName(String project) { - return PROJECT_PATH_TEMPLATE.instantiate("project", project); - } - - /** Parses the project from the given fully-qualified path which represents a project resource. */ - public static final String parseProjectFromProjectName(String projectName) { - return PROJECT_PATH_TEMPLATE.parse(projectName).get("project"); - } - /** Constructs an instance of ReportErrorsServiceClient with default settings. */ public static final ReportErrorsServiceClient create() throws IOException { return create(ReportErrorsServiceSettings.defaultBuilder().build()); @@ -178,9 +165,9 @@ public final ReportErrorsServiceSettings getSettings() { * *

    * try (ReportErrorsServiceClient reportErrorsServiceClient = ReportErrorsServiceClient.create()) {
-   *   String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build();
-   *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(formattedProjectName, event);
+   *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(projectName, event);
    * }
    * 
* @@ -191,10 +178,13 @@ public final ReportErrorsServiceSettings getSettings() { * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final ReportErrorEventResponse reportErrorEvent( - String projectName, ReportedErrorEvent event) { - PROJECT_PATH_TEMPLATE.validate(projectName, "reportErrorEvent"); + ProjectName projectName, ReportedErrorEvent event) { + ReportErrorEventRequest request = - ReportErrorEventRequest.newBuilder().setProjectName(projectName).setEvent(event).build(); + ReportErrorEventRequest.newBuilder() + .setProjectNameWithProjectName(projectName) + .setEvent(event) + .build(); return reportErrorEvent(request); } @@ -213,10 +203,10 @@ public final ReportErrorEventResponse reportErrorEvent( * *

    * try (ReportErrorsServiceClient reportErrorsServiceClient = ReportErrorsServiceClient.create()) {
-   *   String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build();
    *   ReportErrorEventRequest request = ReportErrorEventRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setEvent(event)
    *     .build();
    *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(request);
@@ -245,10 +235,10 @@ public final ReportErrorEventResponse reportErrorEvent(ReportErrorEventRequest r
    *
    * 

    * try (ReportErrorsServiceClient reportErrorsServiceClient = ReportErrorsServiceClient.create()) {
-   *   String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]");
+   *   ProjectName projectName = ProjectName.create("[PROJECT]");
    *   ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build();
    *   ReportErrorEventRequest request = ReportErrorEventRequest.newBuilder()
-   *     .setProjectName(formattedProjectName)
+   *     .setProjectNameWithProjectName(projectName)
    *     .setEvent(event)
    *     .build();
    *   ListenableFuture<ReportErrorEventResponse> future = reportErrorsServiceClient.reportErrorEventCallable().futureCall(request);
diff --git a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/package-info.java b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/package-info.java
index 5254c3f2104a..f48abbe98e8d 100644
--- a/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/package-info.java
+++ b/google-cloud-errorreporting/src/main/java/com/google/cloud/errorreporting/spi/v1beta1/package-info.java
@@ -28,8 +28,8 @@
  * 
  * 
  * try (ErrorGroupServiceClient errorGroupServiceClient = ErrorGroupServiceClient.create()) {
- *   String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
- *   ErrorGroup response = errorGroupServiceClient.getGroup(formattedGroupName);
+ *   GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]");
+ *   ErrorGroup response = errorGroupServiceClient.getGroup(groupName);
  * }
  * 
  * 
@@ -44,8 +44,8 @@ *
  * 
  * try (ErrorStatsServiceClient errorStatsServiceClient = ErrorStatsServiceClient.create()) {
- *   String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]");
- *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(formattedProjectName);
+ *   ProjectName projectName = ProjectName.create("[PROJECT]");
+ *   DeleteEventsResponse response = errorStatsServiceClient.deleteEvents(projectName);
  * }
  * 
  * 
@@ -59,9 +59,9 @@ *
  * 
  * try (ReportErrorsServiceClient reportErrorsServiceClient = ReportErrorsServiceClient.create()) {
- *   String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]");
+ *   ProjectName projectName = ProjectName.create("[PROJECT]");
  *   ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build();
- *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(formattedProjectName, event);
+ *   ReportErrorEventResponse response = reportErrorsServiceClient.reportErrorEvent(projectName, event);
  * }
  * 
  * 
diff --git a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceTest.java b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceTest.java index f58d6313e864..b11c5cb6fd72 100644 --- a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceTest.java +++ b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorGroupServiceTest.java @@ -20,6 +20,7 @@ import com.google.api.gax.testing.MockServiceHelper; import com.google.devtools.clouderrorreporting.v1beta1.ErrorGroup; import com.google.devtools.clouderrorreporting.v1beta1.GetGroupRequest; +import com.google.devtools.clouderrorreporting.v1beta1.GroupName; import com.google.devtools.clouderrorreporting.v1beta1.UpdateGroupRequest; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Status; @@ -78,21 +79,22 @@ public void tearDown() throws Exception { @Test @SuppressWarnings("all") public void getGroupTest() { - String name = "name3373707"; + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); String groupId = "groupId506361563"; - ErrorGroup expectedResponse = ErrorGroup.newBuilder().setName(name).setGroupId(groupId).build(); + ErrorGroup expectedResponse = + ErrorGroup.newBuilder().setNameWithGroupName(name).setGroupId(groupId).build(); mockErrorGroupService.addResponse(expectedResponse); - String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]"); - ErrorGroup actualResponse = client.getGroup(formattedGroupName); + ErrorGroup actualResponse = client.getGroup(groupName); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockErrorGroupService.getRequests(); Assert.assertEquals(1, actualRequests.size()); GetGroupRequest actualRequest = (GetGroupRequest) actualRequests.get(0); - Assert.assertEquals(formattedGroupName, actualRequest.getGroupName()); + Assert.assertEquals(groupName, actualRequest.getGroupNameAsGroupName()); } @Test @@ -102,9 +104,9 @@ public void getGroupExceptionTest() throws Exception { mockErrorGroupService.addException(exception); try { - String formattedGroupName = ErrorGroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName groupName = GroupName.create("[PROJECT]", "[GROUP]"); - client.getGroup(formattedGroupName); + client.getGroup(groupName); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -114,9 +116,10 @@ public void getGroupExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void updateGroupTest() { - String name = "name3373707"; + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); String groupId = "groupId506361563"; - ErrorGroup expectedResponse = ErrorGroup.newBuilder().setName(name).setGroupId(groupId).build(); + ErrorGroup expectedResponse = + ErrorGroup.newBuilder().setNameWithGroupName(name).setGroupId(groupId).build(); mockErrorGroupService.addResponse(expectedResponse); ErrorGroup group = ErrorGroup.newBuilder().build(); diff --git a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceTest.java b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceTest.java index 324a3caaf617..9d8250cbc24a 100644 --- a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceTest.java +++ b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ErrorStatsServiceTest.java @@ -30,6 +30,7 @@ import com.google.devtools.clouderrorreporting.v1beta1.ListEventsResponse; import com.google.devtools.clouderrorreporting.v1beta1.ListGroupStatsRequest; import com.google.devtools.clouderrorreporting.v1beta1.ListGroupStatsResponse; +import com.google.devtools.clouderrorreporting.v1beta1.ProjectName; import com.google.devtools.clouderrorreporting.v1beta1.QueryTimeRange; import com.google.protobuf.GeneratedMessageV3; import io.grpc.Status; @@ -98,11 +99,10 @@ public void listGroupStatsTest() { .build(); mockErrorStatsService.addResponse(expectedResponse); - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); QueryTimeRange timeRange = QueryTimeRange.newBuilder().build(); - ListGroupStatsPagedResponse pagedListResponse = - client.listGroupStats(formattedProjectName, timeRange); + ListGroupStatsPagedResponse pagedListResponse = client.listGroupStats(projectName, timeRange); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); Assert.assertEquals(1, resources.size()); @@ -112,7 +112,7 @@ public void listGroupStatsTest() { Assert.assertEquals(1, actualRequests.size()); ListGroupStatsRequest actualRequest = (ListGroupStatsRequest) actualRequests.get(0); - Assert.assertEquals(formattedProjectName, actualRequest.getProjectName()); + Assert.assertEquals(projectName, actualRequest.getProjectNameAsProjectName()); Assert.assertEquals(timeRange, actualRequest.getTimeRange()); } @@ -123,10 +123,10 @@ public void listGroupStatsExceptionTest() throws Exception { mockErrorStatsService.addException(exception); try { - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); QueryTimeRange timeRange = QueryTimeRange.newBuilder().build(); - client.listGroupStats(formattedProjectName, timeRange); + client.listGroupStats(projectName, timeRange); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -146,10 +146,10 @@ public void listEventsTest() { .build(); mockErrorStatsService.addResponse(expectedResponse); - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); String groupId = "groupId506361563"; - ListEventsPagedResponse pagedListResponse = client.listEvents(formattedProjectName, groupId); + ListEventsPagedResponse pagedListResponse = client.listEvents(projectName, groupId); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); Assert.assertEquals(1, resources.size()); @@ -159,7 +159,7 @@ public void listEventsTest() { Assert.assertEquals(1, actualRequests.size()); ListEventsRequest actualRequest = (ListEventsRequest) actualRequests.get(0); - Assert.assertEquals(formattedProjectName, actualRequest.getProjectName()); + Assert.assertEquals(projectName, actualRequest.getProjectNameAsProjectName()); Assert.assertEquals(groupId, actualRequest.getGroupId()); } @@ -170,10 +170,10 @@ public void listEventsExceptionTest() throws Exception { mockErrorStatsService.addException(exception); try { - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); String groupId = "groupId506361563"; - client.listEvents(formattedProjectName, groupId); + client.listEvents(projectName, groupId); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -186,16 +186,16 @@ public void deleteEventsTest() { DeleteEventsResponse expectedResponse = DeleteEventsResponse.newBuilder().build(); mockErrorStatsService.addResponse(expectedResponse); - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); - DeleteEventsResponse actualResponse = client.deleteEvents(formattedProjectName); + DeleteEventsResponse actualResponse = client.deleteEvents(projectName); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockErrorStatsService.getRequests(); Assert.assertEquals(1, actualRequests.size()); DeleteEventsRequest actualRequest = (DeleteEventsRequest) actualRequests.get(0); - Assert.assertEquals(formattedProjectName, actualRequest.getProjectName()); + Assert.assertEquals(projectName, actualRequest.getProjectNameAsProjectName()); } @Test @@ -205,9 +205,9 @@ public void deleteEventsExceptionTest() throws Exception { mockErrorStatsService.addException(exception); try { - String formattedProjectName = ErrorStatsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); - client.deleteEvents(formattedProjectName); + client.deleteEvents(projectName); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); diff --git a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceTest.java b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceTest.java index 70b1bf877670..913840ab9fe1 100644 --- a/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceTest.java +++ b/google-cloud-errorreporting/src/test/java/com/google/cloud/errorreporting/spi/v1beta1/ReportErrorsServiceTest.java @@ -18,6 +18,7 @@ import com.google.api.gax.grpc.ApiException; import com.google.api.gax.testing.MockGrpcService; import com.google.api.gax.testing.MockServiceHelper; +import com.google.devtools.clouderrorreporting.v1beta1.ProjectName; import com.google.devtools.clouderrorreporting.v1beta1.ReportErrorEventRequest; import com.google.devtools.clouderrorreporting.v1beta1.ReportErrorEventResponse; import com.google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent; @@ -81,17 +82,17 @@ public void reportErrorEventTest() { ReportErrorEventResponse expectedResponse = ReportErrorEventResponse.newBuilder().build(); mockReportErrorsService.addResponse(expectedResponse); - String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build(); - ReportErrorEventResponse actualResponse = client.reportErrorEvent(formattedProjectName, event); + ReportErrorEventResponse actualResponse = client.reportErrorEvent(projectName, event); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockReportErrorsService.getRequests(); Assert.assertEquals(1, actualRequests.size()); ReportErrorEventRequest actualRequest = (ReportErrorEventRequest) actualRequests.get(0); - Assert.assertEquals(formattedProjectName, actualRequest.getProjectName()); + Assert.assertEquals(projectName, actualRequest.getProjectNameAsProjectName()); Assert.assertEquals(event, actualRequest.getEvent()); } @@ -102,10 +103,10 @@ public void reportErrorEventExceptionTest() throws Exception { mockReportErrorsService.addException(exception); try { - String formattedProjectName = ReportErrorsServiceClient.formatProjectName("[PROJECT]"); + ProjectName projectName = ProjectName.create("[PROJECT]"); ReportedErrorEvent event = ReportedErrorEvent.newBuilder().build(); - client.reportErrorEvent(formattedProjectName, event); + client.reportErrorEvent(projectName, event); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); diff --git a/google-cloud-examples/README.md b/google-cloud-examples/README.md index 5dbbffcb9f5e..3deb256e9968 100644 --- a/google-cloud-examples/README.md +++ b/google-cloud-examples/README.md @@ -19,16 +19,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-examples - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-examples:0.8.0' +compile 'com.google.cloud:google-cloud-examples:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-examples" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-examples" % "0.8.2-alpha" ``` To run examples from your command line: diff --git a/google-cloud-examples/pom.xml b/google-cloud-examples/pom.xml index ffbb5f46df5f..495d4e431cb2 100644 --- a/google-cloud-examples/pom.xml +++ b/google-cloud-examples/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-examples diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/PubSubExample.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/PubSubExample.java new file mode 100644 index 000000000000..7bb34839c4a3 --- /dev/null +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/PubSubExample.java @@ -0,0 +1,898 @@ +/* + * 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; + +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.PushConfig; +import com.google.cloud.pubsub.ReceivedMessage; +import com.google.cloud.pubsub.Subscription; +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.ImmutableMap; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An example of using Google BigQuery. + * + *

This example demonstrates a simple/typical Pub/Sub usage. + * + *

See the + * + * README for compilation instructions. Run this code with + *

{@code target/appassembler/bin/PubSubExample
+ *  -Dexec.args="[]
+ *  pull async  ?
+ *  pull sync  
+ *  publish  +
+ *  replace-push-config  ?
+ *  ack  +
+ *  nack  +
+ *  create topic 
+ *  create subscription   ?
+ *  list subscriptions ?
+ *  list topics
+ *  delete topic 
+ *  delete subscription 
+ *  info topic 
+ *  info subscription 
+ *  get-policy topic 
+ *  get-policy subscription 
+ *  add-identity topic   
+ *  add-identity subscription   
+ *  test-permissions topic  +
+ *  test-permissions subscription  +"}
+ * + *

The first parameter is an optional {@code project_id} (logged-in project will be used if not + * supplied). Second parameter is a Pub/Sub operation and can be used to demonstrate its usage. For + * operations that apply to more than one entity (`list`, `create`, `info` and `delete`) the third + * parameter specifies the entity. `pull` operation also takes a third parameter to specify whether + * pulling should be synchronous or asynchronous. + */ +public class PubSubExample { + + private static final Map CREATE_ACTIONS = new HashMap<>(); + private static final Map INFO_ACTIONS = new HashMap<>(); + private static final Map LIST_ACTIONS = new HashMap<>(); + private static final Map DELETE_ACTIONS = new HashMap<>(); + private static final Map PULL_ACTIONS = new HashMap<>(); + private static final Map GET_IAM_ACTIONS = new HashMap<>(); + private static final Map REPLACE_IAM_ACTIONS = new HashMap<>(); + private static final Map TEST_IAM_ACTIONS = new HashMap<>(); + private static final Map ACTIONS = new HashMap<>(); + + private abstract static class PubSubAction { + + abstract void run(PubSub pubsub, T arg) throws Exception; + + abstract T parse(String... args) throws Exception; + + protected String params() { + return ""; + } + } + + private static class Tuple { + + private final X x; + private final Y y; + + private Tuple(X x, Y y) { + this.x = x; + this.y = y; + } + + public static Tuple of(X x, Y y) { + return new Tuple<>(x, y); + } + + X x() { + return x; + } + + Y y() { + return y; + } + } + + private static class ParentAction extends PubSubAction> { + + private final Map subActions; + + ParentAction(Map subActions) { + this.subActions = ImmutableMap.copyOf(subActions); + } + + @Override + @SuppressWarnings("unchecked") + void run(PubSub pubsub, Tuple subaction) throws Exception { + subaction.x().run(pubsub, subaction.y()); + } + + @Override + Tuple parse(String... args) throws Exception { + if (args.length >= 1) { + PubSubAction action = subActions.get(args[0]); + if (action != null) { + Object actionArguments = action.parse(Arrays.copyOfRange(args, 1, args.length)); + return Tuple.of(action, actionArguments); + } else { + throw new IllegalArgumentException("Unrecognized entity '" + args[0] + "'."); + } + } + throw new IllegalArgumentException("Missing required entity."); + } + + @Override + public String params() { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : subActions.entrySet()) { + builder.append('\n').append(entry.getKey()); + String param = entry.getValue().params(); + if (param != null && !param.isEmpty()) { + builder.append(' ').append(param); + } + } + return builder.toString(); + } + } + + private abstract static class NoArgsAction extends PubSubAction { + @Override + Void parse(String... args) throws Exception { + if (args.length == 0) { + return null; + } + throw new IllegalArgumentException("This action takes no arguments."); + } + } + + /** + * This class demonstrates how to list Pub/Sub topics. + * + * @see List + * topics in your project + */ + private static class ListTopicsAction extends NoArgsAction { + @Override + public void run(PubSub pubsub, Void arg) { + Iterator topicIterator = pubsub.listTopics().iterateAll(); + while (topicIterator.hasNext()) { + System.out.println(topicIterator.next()); + } + } + } + + private abstract static class TopicAction extends PubSubAction { + @Override + String parse(String... args) throws Exception { + String message; + if (args.length == 1) { + return args[0]; + } else if (args.length > 1) { + message = "Too many arguments."; + } else { + message = "Missing required topic name."; + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return ""; + } + } + + /** + * This class demonstrates how to retrieve information on a Pub/Sub topic. + */ + private static class TopicInfoAction extends TopicAction { + @Override + public void run(PubSub pubsub, String topic) { + System.out.printf("Topic info: %s%n", pubsub.getTopic(topic)); + } + } + + /** + * This class demonstrates how to create a Pub/Sub topic. + * + * @see Create a topic + */ + private static class CreateTopicAction extends TopicAction { + @Override + public void run(PubSub pubsub, String topic) { + pubsub.create(TopicInfo.of(topic)); + System.out.printf("Created topic %s%n", topic); + } + } + + /** + * This class demonstrates how to delete a Pub/Sub topic. + * + * @see Delete a topic + */ + private static class DeleteTopicAction extends TopicAction { + @Override + public void run(PubSub pubsub, String topic) { + pubsub.deleteTopic(topic); + System.out.printf("Deleted topic %s%n", topic); + } + } + + /** + * This class demonstrates how to list Pub/Sub subscriptions. + * + * @see List subscriptions + */ + private static class ListSubscriptionsAction extends PubSubAction { + @Override + public void run(PubSub pubsub, String topic) { + if (topic == null) { + Iterator subscriptionIterator = pubsub.listSubscriptions().iterateAll(); + while (subscriptionIterator.hasNext()) { + System.out.println(subscriptionIterator.next()); + } + } else { + Iterator subscriptionIdIterator = + pubsub.listSubscriptions(topic).iterateAll(); + while (subscriptionIdIterator.hasNext()) { + System.out.println(subscriptionIdIterator.next()); + } + } + } + + @Override + String parse(String... args) throws Exception { + if (args.length == 1) { + return args[0]; + } else if (args.length == 0) { + return null; + } else { + throw new IllegalArgumentException("Too many arguments."); + } + } + + @Override + public String params() { + return "?"; + } + } + + /** + * This class demonstrates how to publish messages to a Pub/Sub topic. + * + * @see Publish + * messages to a topic + */ + private static class PublishMessagesAction extends PubSubAction>> { + @Override + public void run(PubSub pubsub, Tuple> params) { + String topic = params.x(); + List messages = params.y(); + pubsub.publish(topic, messages); + System.out.printf("Published %d messages to topic %s%n", messages.size(), topic); + } + + @Override + Tuple> parse(String... args) throws Exception { + if (args.length < 2) { + throw new IllegalArgumentException("Missing required topic and messages"); + } + String topic = args[0]; + List messages = new ArrayList<>(); + for (String payload : Arrays.copyOfRange(args, 1, args.length)) { + messages.add(Message.of(payload)); + } + return Tuple.of(topic, messages); + } + + @Override + public String params() { + return " +"; + } + } + + private abstract static class SubscriptionAction extends PubSubAction { + @Override + String parse(String... args) throws Exception { + String message; + if (args.length == 1) { + return args[0]; + } else if (args.length > 1) { + message = "Too many arguments."; + } else { + message = "Missing required subscription name."; + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return ""; + } + } + + /** + * This class demonstrates how to retrieve information on a Pub/Sub subscription. + */ + private static class SubscriptionInfoAction extends SubscriptionAction { + @Override + public void run(PubSub pubsub, String subscription) { + System.out.printf("Subscription info: %s%n", pubsub.getSubscription(subscription)); + } + } + + /** + * This class demonstrates how to create a Pub/Sub subscription. + * + * @see Create a subscription + */ + private static class CreateSubscriptionAction extends PubSubAction { + @Override + public void run(PubSub pubsub, SubscriptionInfo subscription) { + pubsub.create(subscription); + System.out.printf("Created subscription %s%n", subscription.getName()); + } + + @Override + SubscriptionInfo parse(String... args) throws Exception { + String message; + if (args.length > 3) { + message = "Too many arguments."; + } else if (args.length < 2) { + message = "Missing required topic or subscription name"; + } else { + SubscriptionInfo.Builder builder = SubscriptionInfo.newBuilder(args[0], args[1]); + if (args.length == 3) { + builder.setPushConfig(PushConfig.of(args[2])); + } + return builder.build(); + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return " ?"; + } + } + + /** + * This class demonstrates how to delete a Pub/Sub subscription. + */ + private static class DeleteSubscriptionAction extends SubscriptionAction { + @Override + public void run(PubSub pubsub, String subscription) { + pubsub.deleteSubscription(subscription); + System.out.printf("Deleted subscription %s%n", subscription); + } + } + + /** + * This class demonstrates how to modify the push configuration for a Pub/Sub subscription. + * + * @see + * Switching between push and pull delivery + */ + private static class ReplacePushConfigAction extends PubSubAction> { + @Override + public void run(PubSub pubsub, Tuple params) { + String subscription = params.x(); + PushConfig pushConfig = params.y(); + pubsub.replacePushConfig(subscription, pushConfig); + System.out.printf("Set push config %s for subscription %s%n", pushConfig, subscription); + } + + @Override + Tuple parse(String... args) throws Exception { + String message; + if (args.length > 2) { + message = "Too many arguments."; + } else if (args.length < 1) { + message = "Missing required subscription name"; + } else { + String subscription = args[0]; + PushConfig pushConfig = null; + if (args.length == 2) { + pushConfig = PushConfig.of(args[1]); + } + return Tuple.of(subscription, pushConfig); + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return " ?"; + } + } + + private abstract static class MessagesAction extends PubSubAction>> { + @Override + Tuple> parse(String... args) throws Exception { + if (args.length < 2) { + throw new IllegalArgumentException("Missing required subscription and ack IDs"); + } + String subscription = args[0]; + return Tuple.of(subscription, Arrays.asList(Arrays.copyOfRange(args, 1, args.length))); + } + + @Override + public String params() { + return " +"; + } + } + + /** + * This class demonstrates how to acknowledge Pub/Sub messages for a subscription. + * + * @see Receiving + * pull messages + */ + private static class AckMessagesAction extends MessagesAction { + @Override + public void run(PubSub pubsub, Tuple> params) { + String subscription = params.x(); + List ackIds = params.y(); + pubsub.ack(subscription, ackIds); + System.out.printf("Acked %d messages for subscription %s%n", ackIds.size(), subscription); + } + } + + /** + * This class demonstrates how to "nack" Pub/Sub messages for a subscription. This action + * corresponds to setting the acknowledge deadline to 0. + * + * @see Message + * acknowledgement deadline + */ + private static class NackMessagesAction extends MessagesAction { + @Override + public void run(PubSub pubsub, Tuple> params) { + String subscription = params.x(); + List ackIds = params.y(); + pubsub.nack(subscription, ackIds); + System.out.printf("Nacked %d messages for subscription %s%n", ackIds.size(), subscription); + } + } + + /** + * This class demonstrates how modify the acknowledge deadline for messages in a Pub/Sub + * subscription. + * + * @see Message + * acknowledgement deadline + */ + private static class ModifyAckDeadlineAction + extends PubSubAction>> { + + static class SubscriptionAndDeadline { + + private final String subscription; + private final int deadlineMillis; + + private SubscriptionAndDeadline(String subscription, int deadlineMillis) { + this.subscription = subscription; + this.deadlineMillis = deadlineMillis; + } + + String subscription() { + return subscription; + } + + int deadlineMillis() { + return deadlineMillis; + } + } + + @Override + public void run(PubSub pubsub, Tuple> params) + throws Exception { + String subscription = params.x().subscription(); + int deadline = params.x().deadlineMillis(); + List ackIds = params.y(); + pubsub.modifyAckDeadline(subscription, deadline, TimeUnit.MILLISECONDS, ackIds); + System.out.printf("Ack deadline set to %d for %d messages in subscription %s%n", deadline, + ackIds.size(), subscription); + } + + @Override + Tuple> parse(String... args) throws Exception { + if (args.length < 3) { + throw new IllegalArgumentException("Missing required subscription, deadline and ack IDs"); + } + String subscription = args[0]; + int deadline = Integer.parseInt(args[1]); + return Tuple.of(new SubscriptionAndDeadline(subscription, deadline), + Arrays.asList(Arrays.copyOfRange(args, 2, args.length))); + } + + @Override + public String params() { + return " +"; + } + } + + /** + * This class demonstrates how to asynchronously pull messages from a Pub/Sub pull subscription. + * Messages are pulled until a timeout is reached. + * + * @see Receiving + * pull messages + */ + private static class PullAsyncAction extends PubSubAction> { + @Override + public void run(PubSub pubsub, Tuple params) throws Exception { + String subscription = params.x(); + Long timeout = params.y(); + final AtomicInteger messageCount = new AtomicInteger(); + MessageProcessor messageProcessor = new MessageProcessor() { + + @Override + public void process(Message message) throws Exception { + System.out.printf("Received message \"%s\"%n", message); + messageCount.incrementAndGet(); + } + }; + try (PubSub.MessageConsumer consumer = pubsub.pullAsync(subscription, messageProcessor)) { + Thread.sleep(timeout); + } + System.out.printf("Pulled %d messages from subscription %s%n", messageCount.get(), + subscription); + } + + @Override + Tuple parse(String... args) throws Exception { + String message; + if (args.length > 2) { + message = "Too many arguments."; + } else if (args.length < 1) { + message = "Missing required subscription name"; + } else { + String subscription = args[0]; + long timeout = 60_000; + if (args.length == 2) { + timeout = Long.parseLong(args[1]); + } + return Tuple.of(subscription, timeout); + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return " ?"; + } + } + + /** + * This class demonstrates how to synchronously pull messages from a Pub/Sub pull subscription. + * No more than the requested number of messages are pulled. Possibly less messages are pulled. + * + * @see Receiving + * pull messages + */ + private static class PullSyncAction extends PubSubAction> { + @Override + public void run(PubSub pubsub, Tuple params) throws Exception { + String subscription = params.x(); + Integer maxMessages = params.y(); + Iterator messageIterator = pubsub.pull(subscription, maxMessages); + int messageCount = 0; + while (messageIterator.hasNext()) { + ReceivedMessage message = messageIterator.next(); + System.out.printf("Received message \"%s\"%n", message); + message.ack(); + messageCount++; + } + System.out.printf("Pulled %d messages from subscription %s%n", messageCount, subscription); + } + + @Override + Tuple parse(String... args) throws Exception { + String message; + if (args.length == 2) { + String subscription = args[0]; + int maxMessages = Integer.parseInt(args[1]); + return Tuple.of(subscription, maxMessages); + } else if (args.length > 2) { + message = "Too many arguments."; + } else { + message = "Missing required subscription name"; + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return " "; + } + } + + private abstract static class GetPolicyAction extends PubSubAction { + @Override + String parse(String... args) throws Exception { + String message; + if (args.length == 1) { + return args[0]; + } else if (args.length > 1) { + message = "Too many arguments."; + } else { + message = "Missing required resource name"; + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return ""; + } + } + + /** + * This class demonstrates how to get the IAM policy of a topic. + * + * @see Access Control + */ + private static class GetTopicPolicyAction extends GetPolicyAction { + @Override + public void run(PubSub pubsub, String topic) throws Exception { + Policy policy = pubsub.getTopicPolicy(topic); + System.out.printf("Policy for topic %s%n", topic); + System.out.println(policy); + } + } + + /** + * This class demonstrates how to get the IAM policy of a subscription. + * + * @see Access Control + */ + private static class GetSubscriptionPolicyAction extends GetPolicyAction { + @Override + public void run(PubSub pubsub, String subscription) throws Exception { + Policy policy = pubsub.getSubscriptionPolicy(subscription); + System.out.printf("Policy for subscription %s%n", subscription); + System.out.println(policy); + } + } + + private abstract static class AddIdentityAction + extends PubSubAction>> { + @Override + Tuple> parse(String... args) throws Exception { + String message; + if (args.length == 3) { + String resourceName = args[0]; + Role role = Role.of(args[1]); + Identity identity = Identity.valueOf(args[2]); + return Tuple.of(resourceName, Tuple.of(role, identity)); + } else if (args.length > 2) { + message = "Too many arguments."; + } else { + message = "Missing required resource name, role and identity"; + } + throw new IllegalArgumentException(message); + } + + @Override + public String params() { + return " "; + } + } + + /** + * This class demonstrates how to add an identity to a certain role in a topic's IAM policy. + * + * @see Access Control + */ + private static class AddIdentityTopicAction extends AddIdentityAction { + @Override + public void run(PubSub pubsub, Tuple> param) throws Exception { + String topic = param.x(); + Tuple roleAndIdentity = param.y(); + Role role = roleAndIdentity.x(); + Identity identity = roleAndIdentity.y(); + Policy policy = pubsub.getTopicPolicy(topic); + policy = pubsub.replaceTopicPolicy(topic, + policy.toBuilder().addIdentity(role, identity).build()); + System.out.printf("Added role %s to identity %s for topic %s%n", role, identity, topic); + System.out.println(policy); + } + } + + /** + * This class demonstrates how to add an identity to a certain role in a subscription's IAM + * policy. + * + * @see Access Control + */ + private static class AddIdentitySubscriptionAction extends AddIdentityAction { + @Override + public void run(PubSub pubsub, Tuple> param) throws Exception { + String subscription = param.x(); + Tuple roleAndIdentity = param.y(); + Role role = roleAndIdentity.x(); + Identity identity = roleAndIdentity.y(); + Policy policy = pubsub.getSubscriptionPolicy(subscription); + policy = pubsub.replaceSubscriptionPolicy(subscription, + policy.toBuilder().addIdentity(role, identity).build()); + System.out.printf("Added role %s to identity %s for subscription %s%n", role, identity, + subscription); + System.out.println(policy); + } + } + + private abstract static class TestPermissionsAction + extends PubSubAction>> { + @Override + Tuple> parse(String... args) throws Exception { + if (args.length >= 2) { + String resourceName = args[0]; + return Tuple.of(resourceName, Arrays.asList(Arrays.copyOfRange(args, 1, args.length))); + } + throw new IllegalArgumentException("Missing required resource name and permissions"); + } + + @Override + public String params() { + return " +"; + } + } + + /** + * This class demonstrates how to test whether the caller has the provided permissions on a topic. + * + * @see Access Control + */ + private static class TestTopicPermissionsAction extends TestPermissionsAction { + @Override + public void run(PubSub pubsub, Tuple> param) throws Exception { + String topic = param.x(); + List permissions = param.y(); + List booleanPermissions = pubsub.testTopicPermissions(topic, permissions); + System.out.printf("Caller permissions on topic %s%n", topic); + for (int i = 0; i < permissions.size(); i++) { + System.out.printf("%s: %b%n", permissions.get(i), booleanPermissions.get(i)); + } + } + } + + /** + * This class demonstrates how to test whether the caller has the provided permissions on a + * subscription. + * + * @see Access Control + */ + private static class TestSubscriptionPermissionsAction extends TestPermissionsAction { + @Override + public void run(PubSub pubsub, Tuple> param) throws Exception { + String subscription = param.x(); + List permissions = param.y(); + List booleanPermissions = + pubsub.testSubscriptionPermissions(subscription, permissions); + System.out.printf("Caller permissions on subscription %s%n", subscription); + for (int i = 0; i < permissions.size(); i++) { + System.out.printf("%s: %b%n", permissions.get(i), booleanPermissions.get(i)); + } + } + } + + static { + CREATE_ACTIONS.put("topic", new CreateTopicAction()); + CREATE_ACTIONS.put("subscription", new CreateSubscriptionAction()); + INFO_ACTIONS.put("topic", new TopicInfoAction()); + INFO_ACTIONS.put("subscription", new SubscriptionInfoAction()); + LIST_ACTIONS.put("topics", new ListTopicsAction()); + LIST_ACTIONS.put("subscriptions", new ListSubscriptionsAction()); + DELETE_ACTIONS.put("topic", new DeleteTopicAction()); + DELETE_ACTIONS.put("subscription", new DeleteSubscriptionAction()); + PULL_ACTIONS.put("async", new PullAsyncAction()); + PULL_ACTIONS.put("sync", new PullSyncAction()); + GET_IAM_ACTIONS.put("topic", new GetTopicPolicyAction()); + GET_IAM_ACTIONS.put("subscription", new GetSubscriptionPolicyAction()); + REPLACE_IAM_ACTIONS.put("topic", new AddIdentityTopicAction()); + REPLACE_IAM_ACTIONS.put("subscription", new AddIdentitySubscriptionAction()); + TEST_IAM_ACTIONS.put("topic", new TestTopicPermissionsAction()); + TEST_IAM_ACTIONS.put("subscription", new TestSubscriptionPermissionsAction()); + ACTIONS.put("create", new ParentAction(CREATE_ACTIONS)); + ACTIONS.put("info", new ParentAction(INFO_ACTIONS)); + ACTIONS.put("list", new ParentAction(LIST_ACTIONS)); + ACTIONS.put("delete", new ParentAction(DELETE_ACTIONS)); + ACTIONS.put("pull", new ParentAction(PULL_ACTIONS)); + ACTIONS.put("get-policy", new ParentAction(GET_IAM_ACTIONS)); + ACTIONS.put("add-identity", new ParentAction(REPLACE_IAM_ACTIONS)); + ACTIONS.put("test-permissions", new ParentAction(TEST_IAM_ACTIONS)); + ACTIONS.put("publish", new PublishMessagesAction()); + ACTIONS.put("replace-push-config", new ReplacePushConfigAction()); + ACTIONS.put("ack", new AckMessagesAction()); + ACTIONS.put("nack", new NackMessagesAction()); + ACTIONS.put("modify-ack-deadline", new ModifyAckDeadlineAction()); + } + + private static void printUsage() { + StringBuilder actionAndParams = new StringBuilder(); + for (Map.Entry entry : ACTIONS.entrySet()) { + actionAndParams.append("\n\t").append(entry.getKey()); + + String param = entry.getValue().params(); + if (param != null && !param.isEmpty()) { + actionAndParams.append(' ').append(param.replace("\n", "\n\t\t")); + } + } + System.out.printf("Usage: %s [] operation [entity] *%s%n", + PubSubExample.class.getSimpleName(), actionAndParams); + } + + @SuppressWarnings("unchecked") + public static void main(String... args) throws Exception { + if (args.length < 1) { + System.out.println("Missing required project id and action"); + printUsage(); + return; + } + PubSubOptions.Builder optionsBuilder = PubSubOptions.newBuilder(); + PubSubAction action; + String actionName; + if (args.length >= 2 && !ACTIONS.containsKey(args[0])) { + actionName = args[1]; + optionsBuilder.setProjectId(args[0]); + action = ACTIONS.get(args[1]); + args = Arrays.copyOfRange(args, 2, args.length); + } else { + actionName = args[0]; + action = ACTIONS.get(args[0]); + args = Arrays.copyOfRange(args, 1, args.length); + } + if (action == null) { + System.out.println("Unrecognized action."); + printUsage(); + return; + } + try (PubSub pubsub = optionsBuilder.build().getService()) { + Object arg; + try { + arg = action.parse(args); + } catch (IllegalArgumentException ex) { + System.out.printf("Invalid input for action '%s'. %s%n", actionName, ex.getMessage()); + System.out.printf("Expected: %s%n", action.params()); + return; + } catch (Exception ex) { + System.out.println("Failed to parse arguments."); + ex.printStackTrace(); + return; + } + action.run(pubsub, arg); + } + } +} diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/PubSubSnippets.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/PubSubSnippets.java new file mode 100644 index 000000000000..d03cbe8347c8 --- /dev/null +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/PubSubSnippets.java @@ -0,0 +1,1050 @@ +/* + * 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 PubSub's javadoc. Any change to this file should be reflected in + * PubSub'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; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PushConfig; +import com.google.cloud.pubsub.ReceivedMessage; +import com.google.cloud.pubsub.Subscription; +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 java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * This class contains a number of snippets for the {@link PubSub} interface. + */ +public class PubSubSnippets { + + private final PubSub pubsub; + + public PubSubSnippets(PubSub pubsub) { + this.pubsub = pubsub; + } + + /** + * Example of creating a topic. + */ + // [TARGET create(TopicInfo)] + // [VARIABLE "my_topic_name"] + public Topic createTopic(String topicName) { + // [START createTopic] + TopicInfo topicInfo = TopicInfo.of(topicName); + Topic topic = pubsub.create(topicInfo); + // [END createTopic] + return topic; + } + + /** + * Example of asynchronously creating a topic. + */ + // [TARGET createAsync(TopicInfo)] + // [VARIABLE "my_topic_name"] + public Topic createTopicAsync(String topicName) throws ExecutionException, InterruptedException { + // [START createTopicAsync] + TopicInfo topicInfo = TopicInfo.of(topicName); + Future future = pubsub.createAsync(topicInfo); + // ... + Topic topic = future.get(); + // [END createTopicAsync] + return topic; + } + + /** + * Example of getting a topic. + */ + // [TARGET getTopic(String)] + // [VARIABLE "my_topic_name"] + public Topic getTopic(String topicName) { + // [START getTopic] + Topic topic = pubsub.getTopic(topicName); + if (topic == null) { + // topic was not found + } + // [END getTopic] + return topic; + } + + /** + * Example of asynchronously getting a topic. + */ + // [TARGET getTopicAsync(String)] + // [VARIABLE "my_topic_name"] + public Topic getTopicAsync(String topicName) throws ExecutionException, InterruptedException { + // [START getTopicAsync] + Future future = pubsub.getTopicAsync(topicName); + // ... + Topic topic = future.get(); + if (topic == null) { + // topic was not found + } + // [END getTopicAsync] + return topic; + } + + /** + * Example of listing topics, specifying the page size. + */ + // [TARGET listTopics(ListOption...)] + public Page listTopics() { + // [START listTopics] + Page topics = pubsub.listTopics(ListOption.pageSize(100)); + Iterator topicIterator = topics.iterateAll(); + while (topicIterator.hasNext()) { + Topic topic = topicIterator.next(); + // do something with the topic + } + // [END listTopics] + return topics; + } + + /** + * Example of asynchronously listing topics, specifying the page size. + */ + // [TARGET listTopicsAsync(ListOption...)] + public Page listTopicsAsync() throws ExecutionException, InterruptedException { + // [START listTopicsAsync] + Future> future = pubsub.listTopicsAsync(ListOption.pageSize(100)); + // ... + AsyncPage topics = future.get(); + Iterator topicIterator = topics.iterateAll(); + while (topicIterator.hasNext()) { + Topic topic = topicIterator.next(); + // do something with the topic + } + // [END listTopicsAsync] + return topics; + } + + /** + * Example of deleting a topic. + */ + // [TARGET deleteTopic(String)] + // [VARIABLE "my_topic_name"] + public boolean deleteTopic(String topicName) { + // [START deleteTopic] + boolean deleted = pubsub.deleteTopic(topicName); + if (deleted) { + // the topic was deleted + } else { + // the topic was not found + } + // [END deleteTopic] + return deleted; + } + + /** + * Example of asynchronously deleting a topic. + */ + // [TARGET deleteTopicAsync(String)] + // [VARIABLE "my_topic_name"] + public boolean deleteTopicAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START deleteTopicAsync] + Future future = pubsub.deleteTopicAsync(topicName); + // ... + boolean deleted = future.get(); + if (deleted) { + // the topic was deleted + } else { + // the topic was not found + } + // [END deleteTopicAsync] + return deleted; + } + + /** + * Example of publishing one message to a topic. + */ + // [TARGET publish(String, Message)] + // [VARIABLE "my_topic_name"] + public String publishOneMessage(String topicName) { + // [START publishOneMessage] + Message message = Message.of("payload"); + String messageId = pubsub.publish(topicName, message); + // [END publishOneMessage] + return messageId; + } + + /** + * Example of asynchronously publishing one message to a topic. + */ + // [TARGET publishAsync(String, Message)] + // [VARIABLE "my_topic_name"] + public String publishOneMessageAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START publishOneMessageAsync] + Message message = Message.of("payload"); + Future future = pubsub.publishAsync(topicName, message); + // ... + String messageId = future.get(); + // [END publishOneMessageAsync] + return messageId; + } + + /** + * Example of publishing a list of messages to a topic. + */ + // [TARGET publish(String, Iterable)] + // [VARIABLE "my_topic_name"] + public List publishMessageList(String topicName) { + // [START publishMessageList] + List messages = new LinkedList<>(); + messages.add(Message.of("payload1")); + messages.add(Message.of("payload2")); + List messageIds = pubsub.publish(topicName, messages); + // [END publishMessageList] + return messageIds; + } + + /** + * Example of asynchronously publishing a list of messages to a topic. + */ + // [TARGET publishAsync(String, Iterable)] + // [VARIABLE "my_topic_name"] + public List publishMessageListAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START publishMessageListAsync] + List messages = new LinkedList<>(); + messages.add(Message.of("payload1")); + messages.add(Message.of("payload2")); + Future> future = pubsub.publishAsync(topicName, messages); + // ... + List messageIds = future.get(); + // [END publishMessageListAsync] + return messageIds; + } + + /** + * Example of publishing some messages to a topic. + */ + // [TARGET publish(String, Message, Message...)] + // [VARIABLE "my_topic_name"] + public List publishMessages(String topicName) { + // [START publishMessages] + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub.publish(topicName, message1, message2); + // [END publishMessages] + return messageIds; + } + + /** + * Example of asynchronously publishing some messages to a topic. + */ + // [TARGET publishAsync(String, Message, Message...)] + // [VARIABLE "my_topic_name"] + public List publishMessagesAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START publishMessagesAsync] + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Future> future = pubsub.publishAsync(topicName, message1, message2); + // ... + List messageIds = future.get(); + // [END publishMessagesAsync] + return messageIds; + } + + /** + * Example of creating a pull subscription for a topic. + */ + // [TARGET create(SubscriptionInfo)] + // [VARIABLE "my_topic_name"] + // [VARIABLE "my_subscription_name"] + public Subscription createSubscription(String topicName, String subscriptionName) { + // [START createSubscription] + SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(topicName, subscriptionName); + Subscription subscription = pubsub.create(subscriptionInfo); + // [END createSubscription] + return subscription; + } + + /** + * Example of asynchronously creating a pull subscription for a topic. + */ + // [TARGET createAsync(SubscriptionInfo)] + // [VARIABLE "my_topic_name"] + // [VARIABLE "my_subscription_name"] + public Subscription createSubscriptionAsync(String topicName, String subscriptionName) + throws ExecutionException, InterruptedException { + // [START createSubscriptionAsync] + SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(topicName, subscriptionName); + Future future = pubsub.createAsync(subscriptionInfo); + // ... + Subscription subscription = future.get(); + // [END createSubscriptionAsync] + return subscription; + } + + /** + * Example of replacing the push configuration of a subscription, setting the push endpoint. + */ + // [TARGET replacePushConfig(String, PushConfig)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "https://www.example.com/push"] + public void replacePushConfig(String subscriptionName, String endpoint) { + // [START replacePushConfig] + PushConfig pushConfig = PushConfig.of(endpoint); + pubsub.replacePushConfig(subscriptionName, pushConfig); + // [END replacePushConfig] + } + + /** + * Example of replacing the push configuration of a subscription, making it a pull + * subscription. + */ + // [TARGET replacePushConfig(String, PushConfig)] + // [VARIABLE "my_subscription_name"] + public void replacePushConfigToPull(String subscriptionName) { + // [START replacePushConfigToPull] + pubsub.replacePushConfig(subscriptionName, null); + // [END replacePushConfigToPull] + } + + /** + * Example of asynchronously replacing the push configuration of a subscription, setting the + * push endpoint. + */ + // [TARGET replacePushConfigAsync(String, PushConfig)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "https://www.example.com/push"] + public void replacePushConfigAsync(String subscriptionName, String endpoint) + throws ExecutionException, InterruptedException { + // [START replacePushConfigAsync] + PushConfig pushConfig = PushConfig.of(endpoint); + Future future = pubsub.replacePushConfigAsync(subscriptionName, pushConfig); + // ... + future.get(); + // [END replacePushConfigAsync] + } + + /** + * Example of asynchronously replacing the push configuration of a subscription, making it a + * pull subscription. + */ + // [TARGET replacePushConfigAsync(String, PushConfig)] + // [VARIABLE "my_subscription_name"] + public void replacePushConfigToPullAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START replacePushConfigToPullAsync] + Future future = pubsub.replacePushConfigAsync(subscriptionName, null); + // ... + future.get(); + // [END replacePushConfigToPullAsync] + } + + /** + * Example of getting a subscription. + */ + // [TARGET getSubscription(String)] + // [VARIABLE "my_subscription_name"] + public Subscription getSubscription(String subscriptionName) { + // [START getSubscription] + Subscription subscription = pubsub.getSubscription(subscriptionName); + if (subscription == null) { + // subscription was not found + } + // [END getSubscription] + return subscription; + } + + /** + * Example of asynchronously getting a subscription. + */ + // [TARGET getSubscriptionAsync(String)] + // [VARIABLE "my_subscription_name"] + public Subscription getSubscriptionAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START getSubscriptionAsync] + Future future = pubsub.getSubscriptionAsync(subscriptionName); + // ... + Subscription subscription = future.get(); + if (subscription == null) { + // subscription was not found + } + // [END getSubscriptionAsync] + return subscription; + } + + /** + * Example of listing subscriptions, specifying the page size. + */ + // [TARGET listSubscriptions(ListOption...)] + public Page listSubscriptions() { + // [START listSubscriptions] + Page subscriptions = pubsub.listSubscriptions(ListOption.pageSize(100)); + Iterator subscriptionIterator = subscriptions.iterateAll(); + while (subscriptionIterator.hasNext()) { + Subscription subscription = subscriptionIterator.next(); + // do something with the subscription + } + // [END listSubscriptions] + return subscriptions; + } + + /** + * Example of asynchronously listing subscriptions, specifying the page size. + */ + // [TARGET listSubscriptionsAsync(ListOption...)] + public Page listSubscriptionsAsync() throws ExecutionException, InterruptedException { + // [START listSubscriptionsAsync] + Future> future = + pubsub.listSubscriptionsAsync(ListOption.pageSize(100)); + // ... + AsyncPage subscriptions = future.get(); + Iterator subscriptionIterator = subscriptions.iterateAll(); + while (subscriptionIterator.hasNext()) { + Subscription subscription = subscriptionIterator.next(); + // do something with the subscription + } + // [END listSubscriptionsAsync] + return subscriptions; + } + + /** + * Example of listing subscriptions for a topic, specifying the page size. + */ + // [TARGET listSubscriptions(String, ListOption...)] + // [VARIABLE "my_topic_name"] + public Page listSubscriptionsForTopic(String topicName) { + // [START listSubscriptionsForTopic] + Page subscriptions = + pubsub.listSubscriptions(topicName, 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 a topic, specifying the page size. + */ + // [TARGET listSubscriptionsAsync(String, ListOption...)] + // [VARIABLE "my_topic_name"] + public Page listSubscriptionsForTopicAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START listSubscriptionsForTopicAsync] + Future> future = + pubsub.listSubscriptionsAsync(topicName, 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 deleting a subscription. + */ + // [TARGET deleteSubscription(String)] + // [VARIABLE "my_subscription_name"] + public boolean deleteSubscription(String subscriptionName) { + // [START deleteSubscription] + boolean deleted = pubsub.deleteSubscription(subscriptionName); + if (deleted) { + // the subscription was deleted + } else { + // the subscription was not found + } + // [END deleteSubscription] + return deleted; + } + + /** + * Example of asynchronously deleting a subscription. + */ + // [TARGET deleteSubscriptionAsync(String)] + // [VARIABLE "my_subscription_name"] + public boolean deleteSubscriptionAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START deleteSubscriptionAsync] + Future future = pubsub.deleteSubscriptionAsync(subscriptionName); + // ... + boolean deleted = future.get(); + if (deleted) { + // the subscription was deleted + } else { + // the subscription was not found + } + // [END deleteSubscriptionAsync] + return deleted; + } + + /** + * Example of pulling a maximum number of messages from a subscription. + */ + // [TARGET pull(String, int)] + // [VARIABLE "my_subscription_name"] + public void pull(String subscriptionName) { + // [START pull] + Iterator messages = pubsub.pull(subscriptionName, 100); + // Ack deadline is renewed until the message is consumed + while (messages.hasNext()) { + ReceivedMessage message = messages.next(); + // do something with message and ack/nack it + message.ack(); // or message.nack() + } + // [END pull] + } + + /** + * Example of asynchronously pulling a maximum number of messages from a subscription. + */ + // [TARGET pullAsync(String, int)] + // [VARIABLE "my_subscription_name"] + public void pullAsync(String subscriptionName) throws ExecutionException, InterruptedException { + // [START pullAsync] + Future> future = pubsub.pullAsync(subscriptionName, 100); + // ... + Iterator messages = future.get(); + // Ack deadline is renewed until the message is consumed + while (messages.hasNext()) { + ReceivedMessage message = messages.next(); + // do something with message and ack/nack it + message.ack(); // or message.nack() + } + // [END pullAsync] + } + + /** + * Example of continuously pulling messages from a subscription. + */ + // [TARGET pullAsync(String, MessageProcessor, PullOption...)] + // [VARIABLE "my_subscription_name"] + public void pullWithMessageConsumer(String subscriptionName) throws Exception { + // [START pullWithMessageConsumer] + MessageProcessor callback = new MessageProcessor() { + public void process(Message message) throws Exception { + // Ack deadline is renewed until this method returns + // Message is acked if this method returns successfully + // Message is nacked if this method throws an exception + } + }; + PubSub.MessageConsumer consumer = pubsub.pullAsync(subscriptionName, callback); + // ... + // Stop pulling + consumer.close(); + // [END pullWithMessageConsumer] + } + + /** + * Example of acking one message. + */ + // [TARGET ack(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void ackOneMessage(String subscriptionName, String ackId) { + // [START ackOneMessage] + pubsub.ack(subscriptionName, ackId); + // [END ackOneMessage] + } + + /** + * Example of asynchronously acking one message. + */ + // [TARGET ackAsync(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void ackOneMessageAsync(String subscriptionName, String ackId) + throws ExecutionException, InterruptedException { + // [START ackOneMessageAsync] + Future future = pubsub.ackAsync(subscriptionName, ackId); + // ... + future.get(); + // [END ackOneMessageAsync] + } + + /** + * Example of acking more messages. + */ + // [TARGET ack(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void ackMoreMessages(String subscriptionName, String ackId1, String ackId2) { + // [START ackMoreMessages] + pubsub.ack(subscriptionName, ackId1, ackId2); + // [END ackMoreMessages] + } + + /** + * Example of asynchronously acking more messages. + */ + // [TARGET ackAsync(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void ackMoreMessagesAsync(String subscriptionName, String ackId1, String ackId2) + throws ExecutionException, InterruptedException { + // [START ackMoreMessagesAsync] + Future future = pubsub.ackAsync(subscriptionName, ackId1, ackId2); + // ... + future.get(); + // [END ackMoreMessagesAsync] + } + + /** + * Example of acking a list of messages. + */ + // [TARGET ack(String, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void ackMessageList(String subscriptionName, String ackId1, String ackId2) { + // [START ackMessageList] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + pubsub.ack(subscriptionName, ackIds); + // [END ackMessageList] + } + + /** + * Example of asynchronously acking a list of messages. + */ + // [TARGET ackAsync(String, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void ackMessageListAsync(String subscriptionName, String ackId1, String ackId2) + throws ExecutionException, InterruptedException { + // [START ackMessageListAsync] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + Future future = pubsub.ackAsync(subscriptionName, ackIds); + // ... + future.get(); + // [END ackMessageListAsync] + } + + /** + * Example of nacking one message. + */ + // [TARGET nack(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void nackOneMessage(String subscriptionName, String ackId) { + // [START nackOneMessage] + pubsub.nack(subscriptionName, ackId); + // [END nackOneMessage] + } + + /** + * Example of asynchronously nacking one message. + */ + // [TARGET nackAsync(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void nackOneMessageAsync(String subscriptionName, String ackId) + throws ExecutionException, InterruptedException { + // [START nackOneMessageAsync] + Future future = pubsub.nackAsync(subscriptionName, ackId); + // ... + future.get(); + // [END nackOneMessageAsync] + } + + /** + * Example of nacking more messages. + */ + // [TARGET nack(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void nackMoreMessages(String subscriptionName, String ackId1, String ackId2) { + // [START nackMoreMessages] + pubsub.nack(subscriptionName, ackId1, ackId2); + // [END nackMoreMessages] + } + + /** + * Example of asynchronously nacking more messages. + */ + // [TARGET nackAsync(String, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void nackMoreMessagesAsync(String subscriptionName, String ackId1, String ackId2) + throws ExecutionException, InterruptedException { + // [START nackMoreMessagesAsync] + Future future = pubsub.nackAsync(subscriptionName, ackId1, ackId2); + // ... + future.get(); + // [END nackMoreMessagesAsync] + } + + /** + * Example of nacking a list of messages. + */ + // [TARGET nack(String, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void nackMessageList(String subscriptionName, String ackId1, String ackId2) { + // [START nackMessageList] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + pubsub.nack(subscriptionName, ackIds); + // [END nackMessageList] + } + + /** + * Example of asynchronously nacking a list of messages. + */ + // [TARGET nackAsync(String, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void nackMessageListAsync(String subscriptionName, String ackId1, String ackId2) + throws ExecutionException, InterruptedException { + // [START nackMessageListAsync] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + Future future = pubsub.nackAsync(subscriptionName, ackIds); + // ... + future.get(); + // [END nackMessageListAsync] + } + + /** + * Example of modifying the ack deadline of one message. + */ + // [TARGET modifyAckDeadline(String, int, TimeUnit, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void modifyAckDeadlineOneMessage(String subscriptionName, String ackId) { + // [START modifyAckDeadlineOneMessage] + pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackId); + // [END modifyAckDeadlineOneMessage] + } + + /** + * Example of asynchronously modifying the ack deadline of one message. + */ + // [TARGET modifyAckDeadlineAsync(String, int, TimeUnit, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message_ack_id"] + public void modifyAckDeadlineOneMessageAsync(String subscriptionName, String ackId) + throws ExecutionException, InterruptedException { + // [START modifyAckDeadlineOneMessageAsync] + Future future = + pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackId); + // ... + future.get(); + // [END modifyAckDeadlineOneMessageAsync] + } + + /** + * Example of modifying the ack deadline of some messages. + */ + // [TARGET modifyAckDeadline(String, int, TimeUnit, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void modifyAckDeadlineMoreMessages(String subscriptionName, String ackId1, String ackId2) { + // [START modifyAckDeadline] + pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackId1, ackId2); + // [END modifyAckDeadline] + } + + /** + * Example of asynchronously modifying the ack deadline of some messages. + */ + // [TARGET modifyAckDeadlineAsync(String, int, TimeUnit, String, String...)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void modifyAckDeadlineMoreMessagesAsync(String subscriptionName, String ackId1, + String ackId2) throws ExecutionException, InterruptedException { + // [START modifyAckDeadlineMoreMessagesAsync] + Future future = + pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackId1, ackId2); + // ... + future.get(); + // [END modifyAckDeadlineMoreMessagesAsync] + } + + /** + * Example of modifying the ack deadline of a list of messages. + */ + // [TARGET modifyAckDeadline(String, int, TimeUnit, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void modifyAckDeadlineMessageList(String subscriptionName, String ackId1, String ackId2) { + // [START modifyAckDeadlineMessageList] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackIds); + // [END modifyAckDeadlineMessageList] + } + + /** + * Example of asynchronously modifying the ack deadline of a list of messages. + */ + // [TARGET modifyAckDeadlineAsync(String, int, TimeUnit, Iterable)] + // [VARIABLE "my_subscription_name"] + // [VARIABLE "message1_ack_id"] + // [VARIABLE "message2_ack_id"] + public void modifyAckDeadlineMessageListAsync(String subscriptionName, String ackId1, + String ackId2) throws ExecutionException, InterruptedException { + // [START modifyAckDeadlineMessageListAsync] + List ackIds = new LinkedList<>(); + ackIds.add(ackId1); + ackIds.add(ackId2); + Future future = + pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackIds); + // ... + future.get(); + // [END modifyAckDeadlineMessageListAsync] + } + + /** + * Example of getting a topic policy. + */ + // [TARGET getTopicPolicy(String)] + // [VARIABLE "my_topic_name"] + public Policy getTopicPolicy(String topicName) { + // [START getTopicPolicy] + Policy policy = pubsub.getTopicPolicy(topicName); + if (policy == null) { + // topic was not found + } + // [END getTopicPolicy] + return policy; + } + + /** + * Example of asynchronously getting a topic policy. + */ + // [TARGET getTopicPolicyAsync(String)] + // [VARIABLE "my_topic_name"] + public Policy getTopicPolicyAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START getTopicPolicyAsync] + Future future = pubsub.getTopicPolicyAsync(topicName); + // ... + Policy policy = future.get(); + if (policy == null) { + // topic was not found + } + // [END getTopicPolicyAsync] + return policy; + } + + /** + * Example of replacing a topic policy. + */ + // [TARGET replaceTopicPolicy(String, Policy)] + // [VARIABLE "my_topic_name"] + public Policy replaceTopicPolicy(String topicName) { + // [START replaceTopicPolicy] + Policy policy = pubsub.getTopicPolicy(topicName); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + updatedPolicy = pubsub.replaceTopicPolicy(topicName, updatedPolicy); + // [END replaceTopicPolicy] + return updatedPolicy; + } + + /** + * Example of asynchronously replacing a topic policy. + */ + // [TARGET replaceTopicPolicyAsync(String, Policy)] + // [VARIABLE "my_topic_name"] + public Policy replaceTopicPolicyAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START replaceTopicPolicyAsync] + Policy policy = pubsub.getTopicPolicy(topicName); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + Future future = pubsub.replaceTopicPolicyAsync(topicName, updatedPolicy); + // ... + updatedPolicy = future.get(); + // [END replaceTopicPolicyAsync] + return updatedPolicy; + } + + /** + * Example of testing whether the caller has the provided permissions on a topic. + */ + // [TARGET testTopicPermissions(String, List)] + // [VARIABLE "my_topic_name"] + public List testTopicPermissions(String topicName) { + // [START testTopicPermissions] + List permissions = new LinkedList<>(); + permissions.add("pubsub.topics.get"); + List testedPermissions = pubsub.testTopicPermissions(topicName, permissions); + // [END testTopicPermissions] + return testedPermissions; + } + + /** + * Example of asynchronously testing whether the caller has the provided permissions on a topic. + */ + // [TARGET testTopicPermissionsAsync(String, List)] + // [VARIABLE "my_topic_name"] + public List testTopicPermissionsAsync(String topicName) + throws ExecutionException, InterruptedException { + // [START testTopicPermissionsAsync] + List permissions = new LinkedList<>(); + permissions.add("pubsub.topics.get"); + Future> future = pubsub.testTopicPermissionsAsync(topicName, permissions); + // ... + List testedPermissions = future.get(); + // [END testTopicPermissionsAsync] + return testedPermissions; + } + + /** + * Example of getting a subscription policy. + */ + // [TARGET getSubscriptionPolicy(String)] + // [VARIABLE "my_subscription_name"] + public Policy getSubscriptionPolicy(String subscriptionName) { + // [START getSubscriptionPolicy] + Policy policy = pubsub.getSubscriptionPolicy(subscriptionName); + if (policy == null) { + // subscription was not found + } + // [END getSubscriptionPolicy] + return policy; + } + + /** + * Example of asynchronously getting a subscription policy. + */ + // [TARGET getSubscriptionPolicyAsync(String)] + // [VARIABLE "my_subscription_name"] + public Policy getSubscriptionPolicyAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START getSubscriptionPolicyAsync] + Future future = pubsub.getSubscriptionPolicyAsync(subscriptionName); + // ... + Policy policy = future.get(); + if (policy == null) { + // subscription was not found + } + // [END getSubscriptionPolicyAsync] + return policy; + } + + /** + * Example of replacing a subscription policy. + */ + // [TARGET replaceSubscriptionPolicy(String, Policy)] + // [VARIABLE "my_subscription_name"] + public Policy replaceSubscriptionPolicy(String subscriptionName) { + // [START replaceSubscriptionPolicy] + Policy policy = pubsub.getSubscriptionPolicy(subscriptionName); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + updatedPolicy = pubsub.replaceSubscriptionPolicy(subscriptionName, updatedPolicy); + // [END replaceSubscriptionPolicy] + return updatedPolicy; + } + + /** + * Example of asynchronously replacing a subscription policy. + */ + // [TARGET replaceSubscriptionPolicyAsync(String, Policy)] + // [VARIABLE "my_subscription_name"] + public Policy replaceSubscriptionPolicyAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START replaceSubscriptionPolicyAsync] + Policy policy = pubsub.getSubscriptionPolicy(subscriptionName); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + Future future = + pubsub.replaceSubscriptionPolicyAsync(subscriptionName, updatedPolicy); + // ... + updatedPolicy = future.get(); + // [END replaceSubscriptionPolicyAsync] + return updatedPolicy; + } + + /** + * Example of testing whether the caller has the provided permissions on a subscription. + */ + // [TARGET testSubscriptionPermissions(String, List)] + // [VARIABLE "my_subscription_name"] + public List testSubscriptionPermissions(String subscriptionName) { + // [START testSubscriptionPermissions] + List permissions = new LinkedList<>(); + permissions.add("pubsub.subscriptions.get"); + List testedPermissions = + pubsub.testSubscriptionPermissions(subscriptionName, permissions); + // [END testSubscriptionPermissions] + return testedPermissions; + } + + /** + * Example of asynchronously testing whether the caller has the provided permissions on a + * subscription. + */ + // [TARGET testSubscriptionPermissionsAsync(String, List)] + // [VARIABLE "my_subscription_name"] + public List testSubscriptionPermissionsAsync(String subscriptionName) + throws ExecutionException, InterruptedException { + // [START testSubscriptionPermissionsAsync] + List permissions = new LinkedList<>(); + permissions.add("pubsub.subscriptions.get"); + Future> future = + pubsub.testSubscriptionPermissionsAsync(subscriptionName, permissions); + // ... + List testedPermissions = future.get(); + // [END testSubscriptionPermissionsAsync] + return testedPermissions; + } +} diff --git a/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/SubscriptionSnippets.java b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/SubscriptionSnippets.java new file mode 100644 index 000000000000..3e0ac0189fbb --- /dev/null +++ b/google-cloud-examples/src/main/java/com/google/cloud/examples/pubsub/snippets/SubscriptionSnippets.java @@ -0,0 +1,316 @@ +/* + * 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 Subscription's javadoc. Any change to this file should be reflected in + * Subscription's javadoc. + */ + +package com.google.cloud.examples.pubsub.snippets; + +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PushConfig; +import com.google.cloud.pubsub.ReceivedMessage; +import com.google.cloud.pubsub.Subscription; + +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 Subscription} class. + */ +public class SubscriptionSnippets { + + private final Subscription subscription; + + public SubscriptionSnippets(Subscription subscription) { + this.subscription = subscription; + } + + /** + * Example of getting the subscription's latest information. + */ + // [TARGET reload()] + public Subscription reload() { + // [START reload] + Subscription latestSubscription = subscription.reload(); + if (latestSubscription == null) { + // the subscription was not found + } + // [END reload] + return latestSubscription; + } + + /** + * Example of asynchronously getting the subscription's latest information. + */ + // [TARGET reloadAsync()] + public Subscription reloadAsync() throws ExecutionException, InterruptedException { + // [START reloadAsync] + Future future = subscription.reloadAsync(); + // ... + Subscription latestSubscription = future.get(); + if (latestSubscription == null) { + // the subscription was not found + } + // [END reloadAsync] + return latestSubscription; + } + + /** + * Example of deleting the subscription. + */ + // [TARGET delete()] + public boolean delete() { + // [START delete] + boolean deleted = subscription.delete(); + if (deleted) { + // the subscription was deleted + } else { + // the subscription was not found + } + // [END delete] + return deleted; + } + + /** + * Example of asynchronously deleting the subscription. + */ + // [TARGET deleteAsync()] + public boolean deleteAsync() throws ExecutionException, InterruptedException { + // [START deleteAsync] + Future future = subscription.deleteAsync(); + // ... + boolean deleted = future.get(); + if (deleted) { + // the subscription was deleted + } else { + // the subscription was not found + } + // [END deleteAsync] + return deleted; + } + + /** + * Example of replacing the push configuration of the subscription, setting the push endpoint. + */ + // [TARGET replacePushConfig(PushConfig)] + // [VARIABLE "https://www.example.com/push"] + public void replacePushConfig(String endpoint) { + // [START replacePushConfig] + PushConfig pushConfig = PushConfig.of(endpoint); + subscription.replacePushConfig(pushConfig); + // [END replacePushConfig] + } + + /** + * Example of replacing the push configuration of the subscription, making it a pull + * subscription. + */ + // [TARGET replacePushConfig(PushConfig)] + public void replacePushConfigToPull() { + // [START replacePushConfigToPull] + subscription.replacePushConfig(null); + // [END replacePushConfigToPull] + } + + /** + * Example of asynchronously replacing the push configuration of the subscription, setting the + * push endpoint. + */ + // [TARGET replacePushConfigAsync(PushConfig)] + // [VARIABLE "https://www.example.com/push"] + public void replacePushConfigAsync(String endpoint) + throws ExecutionException, InterruptedException { + // [START replacePushConfigAsync] + PushConfig pushConfig = PushConfig.of(endpoint); + Future future = subscription.replacePushConfigAsync(pushConfig); + // ... + future.get(); + // [END replacePushConfigAsync] + } + + /** + * Example of asynchronously replacing the push configuration of the subscription, making it a + * pull subscription. + */ + // [TARGET replacePushConfigAsync(PushConfig)] + public void replacePushConfigToPullAsync() + throws ExecutionException, InterruptedException { + // [START replacePushConfigToPullAsync] + Future future = subscription.replacePushConfigAsync(null); + // ... + future.get(); + // [END replacePushConfigToPullAsync] + } + + /** + * Example of pulling a maximum number of messages from the subscription. + */ + // [TARGET pull(int)] + public void pull() { + // [START pull] + Iterator messages = subscription.pull(100); + // Ack deadline is renewed until the message is consumed + while (messages.hasNext()) { + ReceivedMessage message = messages.next(); + // do something with message and ack/nack it + message.ack(); // or message.nack() + } + // [END pull] + } + + /** + * Example of asynchronously pulling a maximum number of messages from the subscription. + */ + // [TARGET pullAsync(int)] + public void pullAsync() throws ExecutionException, InterruptedException { + // [START pullAsync] + Future> future = subscription.pullAsync(100); + // ... + Iterator messages = future.get(); + // Ack deadline is renewed until the message is consumed + while (messages.hasNext()) { + ReceivedMessage message = messages.next(); + // do something with message and ack/nack it + message.ack(); // or message.nack() + } + // [END pullAsync] + } + + /** + * Example of continuously pulling messages from the subscription. + */ + // [TARGET pullAsync(MessageProcessor, PullOption...)] + // [VARIABLE "my_subscription_name"] + public void pullWithMessageConsumer(String subscriptionName) throws Exception { + // [START pullWithMessageConsumer] + MessageProcessor callback = new MessageProcessor() { + public void process(Message message) throws Exception { + // Ack deadline is renewed until this method returns + // Message is acked if this method returns successfully + // Message is nacked if this method throws an exception + } + }; + MessageConsumer consumer = subscription.pullAsync(callback); + // ... + // Stop pulling + consumer.close(); + // [END pullWithMessageConsumer] + } + + /** + * Example of getting the subscription's policy. + */ + // [TARGET getPolicy()] + public Policy getPolicy() { + // [START getPolicy] + Policy policy = subscription.getPolicy(); + if (policy == null) { + // subscription was not found + } + // [END getPolicy] + return policy; + } + + /** + * Example of asynchronously getting the subscription's policy. + */ + // [TARGET getPolicyAsync()] + public Policy getPolicyAsync() throws ExecutionException, InterruptedException { + // [START getPolicyAsync] + Future future = subscription.getPolicyAsync(); + // ... + Policy policy = future.get(); + if (policy == null) { + // subscription was not found + } + // [END getPolicyAsync] + return policy; + } + + /** + * Example of replacing the subscription's policy. + */ + // [TARGET replacePolicy(Policy)] + public Policy replacePolicy() { + // [START replacePolicy] + Policy policy = subscription.getPolicy(); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + updatedPolicy = subscription.replacePolicy(updatedPolicy); + // [END replacePolicy] + return updatedPolicy; + } + + /** + * Example of asynchronously replacing the subscription's policy. + */ + // [TARGET replacePolicyAsync(Policy)] + public Policy replacePolicyAsync() + throws ExecutionException, InterruptedException { + // [START replacePolicyAsync] + Policy policy = subscription.getPolicy(); + Policy updatedPolicy = policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + Future future = subscription.replacePolicyAsync(updatedPolicy); + // ... + updatedPolicy = future.get(); + // [END replacePolicyAsync] + return updatedPolicy; + } + + /** + * Example of testing whether the caller has the provided permissions on the subscription. + */ + // [TARGET testPermissions(List)] + public List testPermissions() { + // [START testPermissions] + List permissions = new LinkedList<>(); + permissions.add("pubsub.subscriptions.get"); + List testedPermissions = subscription.testPermissions(permissions); + // [END testPermissions] + return testedPermissions; + } + + /** + * Example of asynchronously testing whether the caller has the provided permissions on the + * subscription. + */ + // [TARGET testPermissionsAsync(List)] + public List testPermissionsAsync() + throws ExecutionException, InterruptedException { + // [START testPermissionsAsync] + List permissions = new LinkedList<>(); + permissions.add("pubsub.subscriptions.get"); + Future> future = subscription.testPermissionsAsync(permissions); + // ... + List testedPermissions = future.get(); + // [END testPermissionsAsync] + return testedPermissions; + } +} 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/ITPubSubSnippets.java b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITPubSubSnippets.java new file mode 100644 index 000000000000..81e17dd5d8ba --- /dev/null +++ b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITPubSubSnippets.java @@ -0,0 +1,269 @@ +/* + * 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.assertNotNull; +import static org.junit.Assert.assertNull; +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.ReceivedMessage; +import com.google.cloud.pubsub.Subscription; +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 com.google.common.collect.Sets; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ITPubSubSnippets { + + private static final String NAME_SUFFIX = UUID.randomUUID().toString(); + + private static PubSub pubsub; + private static PubSubSnippets pubsubSnippets; + + @Rule + public Timeout globalTimeout = Timeout.seconds(300); + + private static String formatForTest(String resourceName) { + return resourceName + "-" + NAME_SUFFIX; + } + + @BeforeClass + public static void beforeClass() { + pubsub = PubSubOptions.getDefaultInstance().getService(); + pubsubSnippets = new PubSubSnippets(pubsub); + } + + @AfterClass + public static void afterClass() throws Exception { + if (pubsub != null) { + pubsub.close(); + } + } + + @Test + public void testTopicAndSubscription() throws ExecutionException, InterruptedException { + String topicName1 = formatForTest("topic-name1"); + String topicName2 = formatForTest("topic-name2"); + Topic topic1 = pubsubSnippets.createTopic(topicName1); + Topic topic2 = pubsubSnippets.createTopicAsync(topicName2); + assertNotNull(topic1); + assertNotNull(topic2); + topic1 = pubsubSnippets.getTopic(topicName1); + topic2 = pubsubSnippets.getTopicAsync(topicName2); + assertNotNull(topic1); + assertNotNull(topic2); + Set topics = Sets.newHashSet(pubsubSnippets.listTopics().iterateAll()); + while (!topics.contains(topic1) || !topics.contains(topic2)) { + Thread.sleep(500); + topics = Sets.newHashSet(pubsubSnippets.listTopics().iterateAll()); + } + topics = Sets.newHashSet(pubsubSnippets.listTopicsAsync().iterateAll()); + while (!topics.contains(topic1) || !topics.contains(topic2)) { + Thread.sleep(500); + topics = Sets.newHashSet(pubsubSnippets.listTopicsAsync().iterateAll()); + } + String subscriptionName1 = formatForTest("subscription-name1"); + String subscriptionName2 = formatForTest("subscription-name2"); + Subscription subscription1 = + pubsubSnippets.createSubscription(topicName1, subscriptionName1); + Subscription subscription2 = + pubsubSnippets.createSubscriptionAsync(topicName2, subscriptionName2); + assertNotNull(subscription1); + assertNotNull(subscription2); + Page page = pubsubSnippets.listSubscriptionsForTopic(topicName1); + while (Iterators.size(page.iterateAll()) < 1) { + page = pubsubSnippets.listSubscriptionsForTopic(topicName1); + } + assertEquals(subscriptionName1, page.iterateAll().next().getSubscription()); + page = pubsubSnippets.listSubscriptionsForTopicAsync(topicName2); + while (Iterators.size(page.iterateAll()) < 1) { + page = pubsubSnippets.listSubscriptionsForTopicAsync(topicName2); + } + assertEquals(subscriptionName2, page.iterateAll().next().getSubscription()); + String endpoint = "https://" + pubsub.getOptions().getProjectId() + ".appspot.com/push"; + pubsubSnippets.replacePushConfig(subscriptionName1, endpoint); + pubsubSnippets.replacePushConfigAsync(subscriptionName2, endpoint); + subscription1 = pubsubSnippets.getSubscription(subscriptionName1); + subscription2 = pubsubSnippets.getSubscriptionAsync(subscriptionName2); + assertEquals(endpoint, subscription1.getPushConfig().getEndpoint()); + assertEquals(endpoint, subscription2.getPushConfig().getEndpoint()); + pubsubSnippets.replacePushConfigToPull(subscriptionName1); + pubsubSnippets.replacePushConfigToPullAsync(subscriptionName2); + subscription1 = pubsubSnippets.getSubscription(subscriptionName1); + subscription2 = pubsubSnippets.getSubscriptionAsync(subscriptionName2); + assertNull(subscription1.getPushConfig()); + assertNull(subscription2.getPushConfig()); + assertTrue(pubsubSnippets.deleteTopic(topicName1)); + assertTrue(pubsubSnippets.deleteTopicAsync(topicName2)); + assertTrue(pubsubSnippets.deleteSubscription(subscriptionName1)); + assertTrue(pubsubSnippets.deleteSubscriptionAsync(subscriptionName2)); + } + + @Test + public void testPublishAndPullMessage() throws Exception { + String topicName = formatForTest("topic-name"); + String subscriptionName = formatForTest("subscription-name"); + pubsub.create(TopicInfo.of(topicName)); + pubsub.create(SubscriptionInfo.of(topicName, subscriptionName)); + assertNotNull(pubsubSnippets.publishOneMessage(topicName)); + pubsubSnippets.pull(subscriptionName); + assertNotNull(pubsubSnippets.publishOneMessage(topicName)); + assertEquals(2, pubsubSnippets.publishMessages(topicName).size()); + assertEquals(2, pubsubSnippets.publishMessageList(topicName).size()); + Set messages = new HashSet<>(); + while (messages.size() < 5) { + Iterators.addAll(messages, pubsub.pull(subscriptionName, 100)); + } + Iterator messageIterator = messages.iterator(); + pubsubSnippets.modifyAckDeadlineOneMessage(subscriptionName, messageIterator.next().getAckId()); + pubsubSnippets.modifyAckDeadlineMoreMessages(subscriptionName, + messageIterator.next().getAckId(), messageIterator.next().getAckId()); + pubsubSnippets.modifyAckDeadlineMessageList(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + messageIterator = messages.iterator(); + pubsubSnippets.nackOneMessage(subscriptionName, messageIterator.next().getAckId()); + pubsubSnippets.nackMoreMessages(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + pubsubSnippets.nackMessageList(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + messages.clear(); + while (messages.size() < 5) { + Iterators.addAll(messages, pubsub.pull(subscriptionName, 100)); + } + messageIterator = messages.iterator(); + pubsubSnippets.ackOneMessage(subscriptionName, messageIterator.next().getAckId()); + pubsubSnippets.ackMoreMessages(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + pubsubSnippets.ackMessageList(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + assertTrue(pubsubSnippets.deleteTopic(topicName)); + assertTrue(pubsubSnippets.deleteSubscription(subscriptionName)); + } + + @Test + public void testPublishAndPullMessageAsync() throws Exception { + String topicName = formatForTest("topic-name-async"); + String subscriptionName = formatForTest("subscription-name-async"); + pubsub.create(TopicInfo.of(topicName)); + pubsub.create(SubscriptionInfo.of(topicName, subscriptionName)); + pubsubSnippets.pullWithMessageConsumer(subscriptionName); + assertNotNull(pubsubSnippets.publishOneMessageAsync(topicName)); + pubsubSnippets.pullAsync(subscriptionName); + assertNotNull(pubsubSnippets.publishOneMessageAsync(topicName)); + assertEquals(2, pubsubSnippets.publishMessagesAsync(topicName).size()); + assertEquals(2, pubsubSnippets.publishMessageListAsync(topicName).size()); + Set messages = new HashSet<>(); + while (messages.size() < 5) { + Iterators.addAll(messages, pubsub.pull(subscriptionName, 100)); + } + Iterator messageIterator = messages.iterator(); + pubsubSnippets.modifyAckDeadlineOneMessageAsync(subscriptionName, + messageIterator.next().getAckId()); + pubsubSnippets.modifyAckDeadlineMoreMessagesAsync(subscriptionName, + messageIterator.next().getAckId(), messageIterator.next().getAckId()); + pubsubSnippets.modifyAckDeadlineMessageListAsync(subscriptionName, + messageIterator.next().getAckId(), messageIterator.next().getAckId()); + messageIterator = messages.iterator(); + pubsubSnippets.nackOneMessageAsync(subscriptionName, messageIterator.next().getAckId()); + pubsubSnippets.nackMoreMessagesAsync(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + pubsubSnippets.nackMessageListAsync(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + messages.clear(); + while (messages.size() < 5) { + Iterators.addAll(messages, pubsub.pull(subscriptionName, 100)); + } + messageIterator = messages.iterator(); + pubsubSnippets.ackOneMessageAsync(subscriptionName, messageIterator.next().getAckId()); + pubsubSnippets.ackMoreMessagesAsync(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + pubsubSnippets.ackMessageListAsync(subscriptionName, messageIterator.next().getAckId(), + messageIterator.next().getAckId()); + assertTrue(pubsubSnippets.deleteTopicAsync(topicName)); + assertTrue(pubsubSnippets.deleteSubscriptionAsync(subscriptionName)); + } + + @Test + public void testTopicSubscriptionPolicy() { + String topicName = formatForTest("test-topic-policy"); + Topic topic = pubsubSnippets.createTopic(topicName); + Policy policy = pubsubSnippets.getTopicPolicy(topicName); + assertNotNull(policy); + policy = pubsubSnippets.replaceTopicPolicy(topicName); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = pubsubSnippets.testTopicPermissions(topicName); + assertTrue(permissions.get(0)); + String subscriptionName = formatForTest("test-subscription-policy"); + Subscription subscription = pubsubSnippets.createSubscription(topicName, subscriptionName); + policy = pubsubSnippets.getSubscriptionPolicy(subscriptionName); + assertNotNull(policy); + policy = pubsubSnippets.replaceSubscriptionPolicy(subscriptionName); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + permissions = pubsubSnippets.testSubscriptionPermissions(subscriptionName); + assertTrue(permissions.get(0)); + topic.delete(); + subscription.delete(); + } + + @Test + public void testTopicPolicyAsync() throws ExecutionException, InterruptedException { + String topicName = formatForTest("test-topic-policy-async"); + Topic topic = pubsubSnippets.createTopic(topicName); + Policy policy = pubsubSnippets.getTopicPolicyAsync(topicName); + assertNotNull(policy); + policy = pubsubSnippets.replaceTopicPolicyAsync(topicName); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = pubsubSnippets.testTopicPermissionsAsync(topicName); + assertTrue(permissions.get(0)); + String subscriptionName = formatForTest("test-subscription-policy-async"); + Subscription subscription = pubsubSnippets.createSubscription(topicName, subscriptionName); + policy = pubsubSnippets.getSubscriptionPolicyAsync(subscriptionName); + assertNotNull(policy); + policy = pubsubSnippets.replaceSubscriptionPolicyAsync(subscriptionName); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + permissions = pubsubSnippets.testSubscriptionPermissionsAsync(subscriptionName); + assertTrue(permissions.get(0)); + topic.delete(); + subscription.delete(); + } +} diff --git a/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITSubscriptionSnippets.java b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITSubscriptionSnippets.java new file mode 100644 index 000000000000..ffb912d9f549 --- /dev/null +++ b/google-cloud-examples/src/test/java/com/google/cloud/examples/pubsub/snippets/ITSubscriptionSnippets.java @@ -0,0 +1,125 @@ +/* + * 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.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.ReceivedMessage; +import com.google.cloud.pubsub.Subscription; +import com.google.cloud.pubsub.SubscriptionInfo; +import com.google.cloud.pubsub.Topic; +import com.google.cloud.pubsub.TopicInfo; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Iterator; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ITSubscriptionSnippets { + + private static final String TOPIC = + "it-subscription-snippets-topic-" + UUID.randomUUID().toString(); + private static final String SUBSCRIPTION = + "it-subscription-snippets-subscription-" + UUID.randomUUID().toString(); + private static final Message MESSAGE1 = Message.of("message1"); + private static final Message MESSAGE2 = Message.of("message2"); + + private static PubSub pubsub; + private static Topic topic; + private static Subscription subscription; + + @BeforeClass + public static void beforeClass() { + pubsub = PubSubOptions.getDefaultInstance().getService(); + topic = pubsub.create(TopicInfo.of(TOPIC)); + subscription = pubsub.create(SubscriptionInfo.of(TOPIC, SUBSCRIPTION)); + } + + @AfterClass + public static void afterClass() throws Exception { + if (pubsub != null) { + topic.delete(); + subscription.delete(); + pubsub.close(); + } + } + + @Test + public void testPushConfig() throws ExecutionException, InterruptedException { + SubscriptionSnippets subscriptionSnippets = new SubscriptionSnippets(subscription); + String endpoint = "https://" + pubsub.getOptions().getProjectId() + ".appspot.com/push"; + subscriptionSnippets.replacePushConfig(endpoint); + Subscription updatedSubscription = pubsub.getSubscription(SUBSCRIPTION); + assertEquals(endpoint, updatedSubscription.getPushConfig().getEndpoint()); + subscriptionSnippets.replacePushConfigToPull(); + updatedSubscription = pubsub.getSubscription(SUBSCRIPTION); + assertNull(updatedSubscription.getPushConfig()); + subscriptionSnippets.replacePushConfigAsync(endpoint); + updatedSubscription = pubsub.getSubscription(SUBSCRIPTION); + assertEquals(endpoint, updatedSubscription.getPushConfig().getEndpoint()); + subscriptionSnippets.replacePushConfigToPullAsync(); + updatedSubscription = pubsub.getSubscription(SUBSCRIPTION); + assertNull(updatedSubscription.getPushConfig()); + } + + @Test + public void testPull() throws ExecutionException, InterruptedException { + SubscriptionSnippets subscriptionSnippets = new SubscriptionSnippets(subscription); + pubsub.publish(TOPIC, MESSAGE1, MESSAGE2); + subscriptionSnippets.pull(); + // messages have been acked, we should pull nothing + Iterator iterator = pubsub.pull(SUBSCRIPTION, 2); + assertFalse(iterator.hasNext()); + pubsub.publish(TOPIC, MESSAGE1, MESSAGE2); + subscriptionSnippets.pullAsync(); + // messages have been acked, we should pull nothing + iterator = pubsub.pull(SUBSCRIPTION, 2); + assertFalse(iterator.hasNext()); + subscriptionSnippets.pullAsync(); + } + + @Test + public void testPolicy() throws ExecutionException, InterruptedException { + SubscriptionSnippets subscriptionSnippets = new SubscriptionSnippets(subscription); + Policy policy = subscriptionSnippets.getPolicy(); + assertNotNull(policy); + assertEquals(policy, subscriptionSnippets.getPolicyAsync()); + policy = subscriptionSnippets.replacePolicy(); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + policy = subscription.replacePolicy(policy.toBuilder() + .removeIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()); + assertFalse(policy.getBindings().containsKey(Role.viewer())); + policy = subscriptionSnippets.replacePolicyAsync(); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + assertTrue(subscriptionSnippets.delete()); + assertFalse(subscriptionSnippets.deleteAsync()); + } +} 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..57c3c70d04aa --- /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.getDefaultInstance().getService(); + 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.getValues().iterator()) < 1) { + subscriptions = topicSnippets.listSubscriptionsForTopic(); + } + assertEquals(SUBSCRIPTION, subscriptions.getValues().iterator().next().getSubscription()); + subscriptions = topicSnippets.listSubscriptionsForTopicAsync(); + while (Iterators.size(subscriptions.getValues().iterator()) < 1) { + subscriptions = topicSnippets.listSubscriptionsForTopic(); + } + assertEquals(SUBSCRIPTION, subscriptions.getValues().iterator().next().getSubscription()); + } 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.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + policy = topic.replacePolicy(policy.toBuilder() + .removeIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()); + assertFalse(policy.getBindings().containsKey(Role.viewer())); + policy = topicSnippets.replacePolicyAsync(); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + assertTrue(topicSnippets.delete()); + assertFalse(topicSnippets.deleteAsync()); + } +} diff --git a/google-cloud-language/pom.xml b/google-cloud-language/pom.xml index 2d8c6ed025ad..b3b03b3c7156 100644 --- a/google-cloud-language/pom.xml +++ b/google-cloud-language/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-language @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-language-v1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-logging/README.md b/google-cloud-logging/README.md index e439890b3b2c..796532d4a06d 100644 --- a/google-cloud-logging/README.md +++ b/google-cloud-logging/README.md @@ -26,16 +26,16 @@ Add this to your pom.xml file com.google.cloud google-cloud-logging - 0.8.0-beta + 0.8.2-beta ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-logging:0.8.0-beta' +compile 'com.google.cloud:google-cloud-logging:0.8.2-beta' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-logging" % "0.8.0-beta" +libraryDependencies += "com.google.cloud" % "google-cloud-logging" % "0.8.2-beta" ``` Example Application diff --git a/google-cloud-logging/pom.xml b/google-cloud-logging/pom.xml index 9c5e73155481..727feb6b1399 100644 --- a/google-cloud-logging/pom.xml +++ b/google-cloud-logging/pom.xml @@ -12,7 +12,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-logging @@ -31,7 +31,7 @@ com.google.api.grpc grpc-google-cloud-logging-v2 - 0.1.4 + 0.1.5 io.grpc diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java new file mode 100644 index 000000000000..b710f03f67db --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java @@ -0,0 +1,88 @@ +/* + * 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.logging; + +import java.util.logging.LogRecord; + +import com.google.cloud.MonitoredResource.Builder; + +/** + * A {@link LoggingHandler.Enhancer} that enhances the logging for the + * GAE Flex environment. This enhancer can + * be configured in a logging.properties file with: + * + *

+ * handlers=com.google.cloud.logging.LoggingHandler
+ * com.google.cloud.logging.LoggingHandler.log=gaeflex.log
+ * com.google.cloud.logging.LoggingHandler.resourceType=gae_app
+ * com.google.cloud.logging.LoggingHandler.enhancers=com.google.cloud.logging.GaeFlexLoggingEnhancer
+ * com.google.cloud.logging.LoggingHandler.formatter = java.util.logging.SimpleFormatter
+ * java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s
+ * 
+ * + */ +public class GaeFlexLoggingEnhancer implements LoggingHandler.Enhancer { + + private static final ThreadLocal traceId = new ThreadLocal<>(); + + private final String gaeInstanceId; + + /** + * Set the Trace ID associated with any logging done by the current thread. + * + * @param id The traceID + */ + public static void setCurrentTraceId(String id) { + traceId.set(id); + } + + /** + * Get the Trace ID associated with any logging done by the current thread. + * + * @return id The traceID + */ + public static String getCurrentTraceId() { + return traceId.get(); + } + + public GaeFlexLoggingEnhancer() { + gaeInstanceId = System.getenv("GAE_INSTANCE"); // Are we running on a GAE instance? + } + + @Override + public void enhanceMonitoredResource(Builder builder) { + if (gaeInstanceId != null) { + if (System.getenv("GAE_SERVICE") != null) { + builder.addLabel("module_id", System.getenv("GAE_SERVICE")); + } + if (System.getenv("GAE_VERSION") != null) { + builder.addLabel("version_id", System.getenv("GAE_VERSION")); + } + } + } + + @Override + public void enhanceLogEntry(com.google.cloud.logging.LogEntry.Builder builder, LogRecord record) { + if (gaeInstanceId != null) { + builder.addLabel("appengine.googleapis.com/instance_name", gaeInstanceId); + } + String traceId = getCurrentTraceId(); + if (traceId != null) { + builder.addLabel("appengine.googleapis.com/trace_id", traceId); + } + } +} diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java index e7168d6da678..ecb2706d9528 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java @@ -21,12 +21,11 @@ import com.google.cloud.MonitoredResource; import com.google.cloud.logging.Logging.WriteOption; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Set; import java.util.logging.ErrorManager; import java.util.logging.Filter; import java.util.logging.Formatter; @@ -79,6 +78,11 @@ *
  • {@code com.google.cloud.logging.LoggingHandler.flushLevel} specifies the flush log level. * When a log with this level is published, logs are transmitted to the Stackdriver Logging * service (defaults to {@link LoggingLevel#ERROR}). + *
  • {@code com.google.cloud.logging.LoggingHandler.enhancers} specifies a comma separated list + * of {@link Enhancer} classes. This handler will call each enhancer list whenever it builds + * a {@link MonitoredResource} or {@link LogEntry} instance (defaults to empty list). + *
  • {@code com.google.cloud.logging.LoggingHandler.resourceType} the type name to use when + * creating the default {@link MonitoredResource} (defaults to "global"). * * *

    To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite @@ -93,15 +97,16 @@ public class LoggingHandler extends Handler { private static final String HANDLERS_PROPERTY = "handlers"; private static final String ROOT_LOGGER_NAME = ""; private static final String[] NO_HANDLERS = new String[0]; - private static final Set EXCLUDED_LOGGERS = ImmutableSet.of("io.grpc", "io.netty", - "com.google.api.client.http", "sun.net.www.protocol.http"); + + private static final ThreadLocal inPublishCall = new ThreadLocal<>(); private final LoggingOptions options; - private final List buffer = new LinkedList<>(); private final WriteOption[] writeOptions; - private Logging logging; + private List buffer = new LinkedList<>(); + private volatile Logging logging; private Level flushLevel; private long flushSize; + private final List enhancers; /** * Creates an handler that publishes messages to Stackdriver Logging. @@ -134,9 +139,30 @@ public LoggingHandler(String log, LoggingOptions options) { * * @param log the name of the log to which log entries are written * @param options options for the Stackdriver Logging service - * @param monitoredResource the monitored resource to which log entries refer + * @param monitoredResource the monitored resource to which log entries refer. If it is null + * then a default resource is created based on the project ID. When creating a default resource, if + * any {@link Enhancer} instances are configured and then each + * {@link Enhancer#enhanceMonitoredResource(com.google.cloud.MonitoredResource.Builder)} method + * is called before building the default resource. */ public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) { + this(log, options, monitoredResource,null); + } + + /** + * Creates a handler that publishes messages to Stackdriver Logging. + * + * @param log the name of the log to which log entries are written + * @param options options for the Stackdriver Logging service + * @param monitoredResource the monitored resource to which log entries refer. If it is null + * then a default resource is created based on the project ID. When creating a default resource, if + * any {@link Enhancer} instances are configured and then each + * {@link Enhancer#enhanceMonitoredResource(com.google.cloud.MonitoredResource.Builder)} method + * is called before building the default resource. + * @param enhancers List of {@link Enhancer} instances used to enhance any {@link MonitoredResource} + * or {@link LogEntry} instances built by this handler. + */ + public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource, List enhancers) { LogConfigHelper helper = new LogConfigHelper(); String className = getClass().getName(); this.options = options != null ? options : LoggingOptions.getDefaultInstance(); @@ -146,33 +172,10 @@ public LoggingHandler(String log, LoggingOptions options, MonitoredResource moni setFilter(helper.getFilterProperty(className + ".filter", null)); setFormatter(helper.getFormatterProperty(className + ".formatter", new SimpleFormatter())); String logName = firstNonNull(log, helper.getProperty(className + ".log", "java.log")); - MonitoredResource resource = firstNonNull(monitoredResource, getDefaultResource()); + this.enhancers = enhancers != null ? enhancers : helper.getEnhancerProperty(className + ".enhancers"); + String resourceType = helper.getProperty(className + ".resourceType", "global"); + MonitoredResource resource = monitoredResource != null ? monitoredResource : getDefaultResource(resourceType); writeOptions = new WriteOption[]{WriteOption.logName(logName), WriteOption.resource(resource)}; - maskLoggers(); - } - - private static void maskLoggers() { - for (String loggerName : EXCLUDED_LOGGERS) { - Logger logger = Logger.getLogger(loggerName); - // We remove the Clould Logging handler if it has been registered for a logger that should be - // masked - List loggingHandlers = getLoggingHandlers(logger); - for (LoggingHandler loggingHandler : loggingHandlers) { - logger.removeHandler(loggingHandler); - } - // We mask ancestors if they have a Stackdriver Logging Handler registered - Logger currentLogger = logger; - Logger ancestor = currentLogger.getParent(); - boolean masked = false; - while (ancestor != null && !masked) { - if (hasLoggingHandler(ancestor)) { - currentLogger.setUseParentHandlers(false); - masked = true; - } - currentLogger = ancestor; - ancestor = ancestor.getParent(); - } - } } private static List getLoggingHandlers(Logger logger) { @@ -206,8 +209,13 @@ private static boolean hasLoggingHandler(Logger logger) { return false; } - private MonitoredResource getDefaultResource() { - return MonitoredResource.of("global", ImmutableMap.of("project_id", options.getProjectId())); + private MonitoredResource getDefaultResource(String resourceType) { + MonitoredResource.Builder builder = MonitoredResource.newBuilder(resourceType); + builder.addLabel("project_id", options.getProjectId()); + for (Enhancer enhancer : enhancers) { + enhancer.enhanceMonitoredResource(builder); + } + return builder.build(); } private static class LogConfigHelper { @@ -265,6 +273,24 @@ Formatter getFormatterProperty(String name, Formatter defaultValue) { } return defaultValue; } + + List getEnhancerProperty(String name) { + String list = manager.getProperty(name); + try { + List enhancers = new ArrayList<>(); + if (list != null) { + String[] items = list.split(","); + for (String e_name : items) { + Class clz = (Class) ClassLoader.getSystemClassLoader().loadClass(e_name); + enhancers.add((Enhancer) clz.newInstance()); + } + } + return enhancers; + } catch (Exception ex) { + // If we cannot create the enhancers we fall back to the default + } + return Collections.emptyList(); + } } /** @@ -272,23 +298,55 @@ Formatter getFormatterProperty(String name, Formatter defaultValue) { */ Logging getLogging() { if (logging == null) { - logging = options.getService(); + synchronized (this) { + if (logging == null) { + logging = options.getService(); + } + } } return logging; } @Override - public synchronized void publish(LogRecord record) { + public void publish(LogRecord record) { // check that the log record should be logged if (!isLoggable(record)) { return; } - LogEntry entry = entryFor(record); - if (entry != null) { - buffer.add(entry); + + // HACK warning: this logger doesn't work like normal loggers; the log calls are issued + // from another class instead of by itself, so it can't be configured off like normal + // loggers. We have to check the source class name instead. + if ("io.netty.handler.codec.http2.Http2FrameLogger".equals(record.getSourceClassName())) { + return; + } + + if (inPublishCall.get() != null) { + // ignore all logs generated in the course of logging through this handler + return; } - if (buffer.size() >= flushSize || record.getLevel().intValue() >= flushLevel.intValue()) { - flush(); + inPublishCall.set(true); + + try { + LogEntry entry = entryFor(record); + + List flushBuffer = null; + WriteOption[] flushWriteOptions = null; + + synchronized (this) { + if (entry != null) { + buffer.add(entry); + } + if (buffer.size() >= flushSize || record.getLevel().intValue() >= flushLevel.intValue()) { + flushBuffer = buffer; + flushWriteOptions = writeOptions; + buffer = new LinkedList<>(); + } + } + + flush(flushBuffer, flushWriteOptions); + } finally { + inPublishCall.remove(); } } @@ -305,13 +363,18 @@ private LogEntry entryFor(LogRecord record) { LogEntry.Builder builder = LogEntry.newBuilder(Payload.StringPayload.of(payload)) .addLabel("levelName", level.getName()) .addLabel("levelValue", String.valueOf(level.intValue())) + .setTimestamp(record.getMillis()) .setSeverity(severityFor(level)); + + for (Enhancer enhancer : enhancers) { + enhancer.enhanceLogEntry(builder, record); + } enhanceLogEntry(builder, record); return builder.build(); } + @Deprecated protected void enhanceLogEntry(LogEntry.Builder builder, LogRecord record) { - // no-op in this class } private static Severity severityFor(Level level) { @@ -350,18 +413,35 @@ private static Severity severityFor(Level level) { * how entries should be written. */ void write(List entries, WriteOption... options) { - getLogging().write(entries, options); + getLogging().writeAsync(entries, options); } @Override - public synchronized void flush() { + public void flush() { + List flushBuffer; + WriteOption[] flushWriteOptions; + + synchronized (this) { + if (buffer.isEmpty()) { + return; + } + flushBuffer = buffer; + flushWriteOptions = writeOptions; + buffer = new LinkedList<>(); + } + + flush(flushBuffer, flushWriteOptions); + } + + private void flush(List flushBuffer, WriteOption[] flushWriteOptions) { + if (flushBuffer == null) { + return; + } try { - write(buffer, writeOptions); + write(flushBuffer, flushWriteOptions); } catch (Exception ex) { // writing can fail but we should not throw an exception, we report the error instead reportError(null, ex, ErrorManager.FLUSH_FAILURE); - } finally { - buffer.clear(); } } @@ -407,6 +487,15 @@ public synchronized long setFlushSize(long flushSize) { */ public static void addHandler(Logger logger, LoggingHandler handler) { logger.addHandler(handler); - maskLoggers(); } + + /** + * A Log Enhancer. + * May be used to enhance the {@link MonitoredResource} and/or the {@link LogEntry} + */ + interface Enhancer { + void enhanceMonitoredResource(MonitoredResource.Builder builder); + void enhanceLogEntry(LogEntry.Builder builder, LogRecord record); + } + } diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java index 47dfc175d403..b7bf7161a04f 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java @@ -40,7 +40,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; import com.google.logging.v2.CreateLogMetricRequest; import com.google.logging.v2.CreateSinkRequest; @@ -107,9 +106,6 @@ private static V get(Future future) { private static Future transform(Future future, Function function) { - if (future instanceof ListenableFuture) { - return Futures.transform((ListenableFuture) future, function); - } return Futures.lazyTransform(future, function); } diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/DefaultLoggingRpc.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/DefaultLoggingRpc.java index dd1ed10f40f2..1673151a315b 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/DefaultLoggingRpc.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/DefaultLoggingRpc.java @@ -16,6 +16,8 @@ package com.google.cloud.logging.spi; +import com.google.api.gax.core.Function; +import com.google.api.gax.core.RpcFuture; import com.google.api.gax.grpc.ApiException; import com.google.api.gax.grpc.ChannelProvider; import com.google.api.gax.grpc.ExecutorProvider; @@ -33,10 +35,6 @@ import com.google.cloud.logging.spi.v2.LoggingServiceV2Settings; import com.google.cloud.logging.spi.v2.MetricsServiceV2Client; import com.google.cloud.logging.spi.v2.MetricsServiceV2Settings; -import com.google.common.base.Function; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.logging.v2.CreateLogMetricRequest; import com.google.logging.v2.CreateSinkRequest; import com.google.logging.v2.DeleteLogMetricRequest; @@ -59,13 +57,13 @@ import com.google.logging.v2.WriteLogEntriesRequest; import com.google.logging.v2.WriteLogEntriesResponse; import com.google.protobuf.Empty; - import io.grpc.ManagedChannel; import io.grpc.Status.Code; import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; - import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -148,21 +146,25 @@ public DefaultLoggingRpc(LoggingOptions options) throws IOException { } } - private static Future translate(ListenableFuture from, final boolean idempotent, - int... returnNullOn) { - final Set returnNullOnSet = Sets.newHashSetWithExpectedSize(returnNullOn.length); - for (int value : returnNullOn) { - returnNullOnSet.add(value); + private static Future translate( + RpcFuture from, final boolean idempotent, Code... returnNullOn) { + final Set returnNullOnSet; + if (returnNullOn.length > 0) { + returnNullOnSet = EnumSet.of(returnNullOn[0], returnNullOn); + } else { + returnNullOnSet = Collections.emptySet(); } - return Futures.catching(from, ApiException.class, new Function() { - @Override - public V apply(ApiException exception) { - if (returnNullOnSet.contains(exception.getStatusCode().value())) { - return null; - } - throw new LoggingException(exception, idempotent); - } - }); + return from.catching( + ApiException.class, + new Function() { + @Override + public V apply(ApiException exception) { + if (returnNullOnSet.contains(exception.getStatusCode().value())) { + return null; + } + throw new LoggingException(exception, idempotent); + } + }); } @Override @@ -177,7 +179,7 @@ public Future update(UpdateSinkRequest request) { @Override public Future get(GetSinkRequest request) { - return translate(configClient.getSinkCallable().futureCall(request), true, Code.NOT_FOUND.value()); + return translate(configClient.getSinkCallable().futureCall(request), true, Code.NOT_FOUND); } @Override @@ -187,14 +189,12 @@ public Future list(ListSinksRequest request) { @Override public Future delete(DeleteSinkRequest request) { - return translate(configClient.deleteSinkCallable().futureCall(request), true, - Code.NOT_FOUND.value()); + return translate(configClient.deleteSinkCallable().futureCall(request), true, Code.NOT_FOUND); } @Override public Future delete(DeleteLogRequest request) { - return translate(loggingClient.deleteLogCallable().futureCall(request), true, - Code.NOT_FOUND.value()); + return translate(loggingClient.deleteLogCallable().futureCall(request), true, Code.NOT_FOUND); } @Override @@ -226,8 +226,8 @@ public Future update(UpdateLogMetricRequest request) { @Override public Future get(GetLogMetricRequest request) { - return translate(metricsClient.getLogMetricCallable().futureCall(request), true, - Code.NOT_FOUND.value()); + return translate( + metricsClient.getLogMetricCallable().futureCall(request), true, Code.NOT_FOUND); } @Override @@ -237,8 +237,8 @@ public Future list(ListLogMetricsRequest request) { @Override public Future delete(DeleteLogMetricRequest request) { - return translate(metricsClient.deleteLogMetricCallable().futureCall(request), true, - Code.NOT_FOUND.value()); + return translate( + metricsClient.deleteLogMetricCallable().futureCall(request), true, Code.NOT_FOUND); } @Override diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/ConfigServiceV2Client.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/ConfigServiceV2Client.java index c6e570a2b577..6a930936adf8 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/ConfigServiceV2Client.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/ConfigServiceV2Client.java @@ -184,8 +184,8 @@ public final ConfigServiceV2Settings getSettings() { * } *

  • * - * @param parent Required. The resource name where this sink was created: - *

    "projects/[PROJECT_ID]" "organizations/[ORGANIZATION_ID]" + * @param parent Required. The parent resource whose sinks are to be listed. Examples: + * `"projects/my-logging-project"`, `"organizations/123456789"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final ListSinksPagedResponse listSinks(ParentNameOneof parent) { @@ -287,9 +287,10 @@ public final UnaryCallable listSinksCallabl * } *

    * - * @param sinkName Required. The resource name of the sink to return: + * @param sinkName Required. The parent resource name of the sink: *

    "projects/[PROJECT_ID]/sinks/[SINK_ID]" * "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]" + *

    Example: `"projects/my-project-id/sinks/my-sink-id"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final LogSink getSink(SinkNameOneof sinkName) { @@ -346,7 +347,10 @@ public final UnaryCallable getSinkCallable() { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Creates a sink. + * Creates a sink that exports specified log entries to a destination. The export of + * newly-ingested log entries begins immediately, unless the current time is outside the sink's + * start and end times or the sink's `writer_identity` is not permitted to write to the + * destination. A sink can export log entries only from the resource owning the sink. * *

    Sample code: * @@ -360,6 +364,7 @@ public final UnaryCallable getSinkCallable() { * * @param parent Required. The resource in which to create the sink: *

    "projects/[PROJECT_ID]" "organizations/[ORGANIZATION_ID]" + *

    Examples: `"projects/my-logging-project"`, `"organizations/123456789"`. * @param sink Required. The new sink, whose `name` parameter is a sink identifier that is not * already in use. * @throws com.google.api.gax.grpc.ApiException if the remote call fails @@ -373,7 +378,10 @@ public final LogSink createSink(ParentNameOneof parent, LogSink sink) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Creates a sink. + * Creates a sink that exports specified log entries to a destination. The export of + * newly-ingested log entries begins immediately, unless the current time is outside the sink's + * start and end times or the sink's `writer_identity` is not permitted to write to the + * destination. A sink can export log entries only from the resource owning the sink. * *

    Sample code: * @@ -398,7 +406,10 @@ public final LogSink createSink(CreateSinkRequest request) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Creates a sink. + * Creates a sink that exports specified log entries to a destination. The export of + * newly-ingested log entries begins immediately, unless the current time is outside the sink's + * start and end times or the sink's `writer_identity` is not permitted to write to the + * destination. A sink can export log entries only from the resource owning the sink. * *

    Sample code: * @@ -422,7 +433,12 @@ public final UnaryCallable createSinkCallable() { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Updates or creates a sink. + * Updates a sink. If the named sink doesn't exist, then this method is identical to + * [sinks.create](/logging/docs/api/reference/rest/v2/projects.sinks/create). If the named sink + * does exist, then this method replaces the following fields in the existing sink with values + * from the new sink: `destination`, `filter`, `output_version_format`, `start_time`, and + * `end_time`. The updated filter might also have a new `writer_identity`; see the + * `unique_writer_identity` field. * *

    Sample code: * @@ -434,13 +450,13 @@ public final UnaryCallable createSinkCallable() { * } *

    * - * @param sinkName Required. The resource name of the sink to update, including the parent + * @param sinkName Required. The full resource name of the sink to update, including the parent * resource and the sink identifier: *

    "projects/[PROJECT_ID]/sinks/[SINK_ID]" * "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]" *

    Example: `"projects/my-project-id/sinks/my-sink-id"`. * @param sink Required. The updated sink, whose name is the same identifier that appears as part - * of `sinkName`. If `sinkName` does not exist, then this method creates a new sink. + * of `sink_name`. If `sink_name` does not exist, then this method creates a new sink. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final LogSink updateSink(SinkNameOneof sinkName, LogSink sink) { @@ -452,7 +468,12 @@ public final LogSink updateSink(SinkNameOneof sinkName, LogSink sink) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Updates or creates a sink. + * Updates a sink. If the named sink doesn't exist, then this method is identical to + * [sinks.create](/logging/docs/api/reference/rest/v2/projects.sinks/create). If the named sink + * does exist, then this method replaces the following fields in the existing sink with values + * from the new sink: `destination`, `filter`, `output_version_format`, `start_time`, and + * `end_time`. The updated filter might also have a new `writer_identity`; see the + * `unique_writer_identity` field. * *

    Sample code: * @@ -477,7 +498,12 @@ public final LogSink updateSink(UpdateSinkRequest request) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Updates or creates a sink. + * Updates a sink. If the named sink doesn't exist, then this method is identical to + * [sinks.create](/logging/docs/api/reference/rest/v2/projects.sinks/create). If the named sink + * does exist, then this method replaces the following fields in the existing sink with values + * from the new sink: `destination`, `filter`, `output_version_format`, `start_time`, and + * `end_time`. The updated filter might also have a new `writer_identity`; see the + * `unique_writer_identity` field. * *

    Sample code: * @@ -501,7 +527,8 @@ public final UnaryCallable updateSinkCallable() { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Deletes a sink. + * Deletes a sink. If the sink has a unique `writer_identity`, then that service account is also + * deleted. * *

    Sample code: * @@ -512,11 +539,12 @@ public final UnaryCallable updateSinkCallable() { * } *

    * - * @param sinkName Required. The resource name of the sink to delete, including the parent + * @param sinkName Required. The full resource name of the sink to delete, including the parent * resource and the sink identifier: *

    "projects/[PROJECT_ID]/sinks/[SINK_ID]" * "organizations/[ORGANIZATION_ID]/sinks/[SINK_ID]" - *

    It is an error if the sink does not exist. + *

    It is an error if the sink does not exist. Example: + * `"projects/my-project-id/sinks/my-sink-id"`. It is an error if the sink does not exist. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final void deleteSink(SinkNameOneof sinkName) { @@ -528,7 +556,8 @@ public final void deleteSink(SinkNameOneof sinkName) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Deletes a sink. + * Deletes a sink. If the sink has a unique `writer_identity`, then that service account is also + * deleted. * *

    Sample code: * @@ -551,7 +580,8 @@ private final void deleteSink(DeleteSinkRequest request) { // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Deletes a sink. + * Deletes a sink. If the sink has a unique `writer_identity`, then that service account is also + * deleted. * *

    Sample code: * diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Client.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Client.java index 6948bba84a87..502d6b1d950e 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Client.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Client.java @@ -16,6 +16,7 @@ package com.google.cloud.logging.spi.v2; import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogEntriesPagedResponse; +import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogsPagedResponse; import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListMonitoredResourceDescriptorsPagedResponse; import com.google.api.MonitoredResource; @@ -24,10 +25,13 @@ import com.google.logging.v2.DeleteLogRequest; import com.google.logging.v2.ListLogEntriesRequest; import com.google.logging.v2.ListLogEntriesResponse; +import com.google.logging.v2.ListLogsRequest; +import com.google.logging.v2.ListLogsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse; import com.google.logging.v2.LogEntry; import com.google.logging.v2.LogNameOneof; +import com.google.logging.v2.ParentNameOneof; import com.google.logging.v2.WriteLogEntriesRequest; import com.google.logging.v2.WriteLogEntriesResponse; import com.google.protobuf.Empty; @@ -115,6 +119,8 @@ public class LoggingServiceV2Client implements AutoCloseable { private final UnaryCallable< ListMonitoredResourceDescriptorsRequest, ListMonitoredResourceDescriptorsPagedResponse> listMonitoredResourceDescriptorsPagedCallable; + private final UnaryCallable listLogsCallable; + private final UnaryCallable listLogsPagedCallable; /** Constructs an instance of LoggingServiceV2Client with default settings. */ public static final LoggingServiceV2Client create() throws IOException { @@ -145,6 +151,9 @@ protected LoggingServiceV2Client(LoggingServiceV2Settings settings) throws IOExc UnaryCallable.create(settings.deleteLogSettings(), this.channel, this.executor); this.writeLogEntriesCallable = UnaryCallable.create(settings.writeLogEntriesSettings(), this.channel, this.executor); + if (settings.writeLogEntriesSettings().getBundlerFactory() != null) { + closeables.add(settings.writeLogEntriesSettings().getBundlerFactory()); + } this.listLogEntriesCallable = UnaryCallable.create(settings.listLogEntriesSettings(), this.channel, this.executor); this.listLogEntriesPagedCallable = @@ -156,6 +165,10 @@ protected LoggingServiceV2Client(LoggingServiceV2Settings settings) throws IOExc this.listMonitoredResourceDescriptorsPagedCallable = UnaryCallable.createPagedVariant( settings.listMonitoredResourceDescriptorsSettings(), this.channel, this.executor); + this.listLogsCallable = + UnaryCallable.create(settings.listLogsSettings(), this.channel, this.executor); + this.listLogsPagedCallable = + UnaryCallable.createPagedVariant(settings.listLogsSettings(), this.channel, this.executor); if (settings.getChannelProvider().shouldAutoClose()) { closeables.add( @@ -356,8 +369,8 @@ public final WriteLogEntriesResponse writeLogEntries(WriteLogEntriesRequest requ // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists log entries. Use this method to retrieve log entries from Cloud Logging. For ways to - * export log entries, see [Exporting Logs](/logging/docs/export). + * Lists log entries. Use this method to retrieve log entries from Stackdriver Logging. For ways + * to export log entries, see [Exporting Logs](/logging/docs/export). * *

    Sample code: * @@ -372,13 +385,16 @@ public final WriteLogEntriesResponse writeLogEntries(WriteLogEntriesRequest requ * } *

    * - * @param resourceNames Required. One or more cloud resources from which to retrieve log entries: + * @param resourceNames Required. Names of one or more resources from which to retrieve log + * entries: *

    "projects/[PROJECT_ID]" "organizations/[ORGANIZATION_ID]" *

    Projects listed in the `project_ids` field are added to this list. * @param filter Optional. A filter that chooses which log entries to return. See [Advanced Logs * Filters](/logging/docs/view/advanced_filters). Only log entries that match the filter are - * returned. An empty filter matches all log entries. The maximum length of the filter is - * 20000 characters. + * returned. An empty filter matches all log entries in the resources listed in + * `resource_names`. Referencing a parent resource that is not listed in `resource_names` will + * cause the filter to return no results. The maximum length of the filter is 20000 + * characters. * @param orderBy Optional. How the results should be sorted. Presently, the only permitted values * are `"timestamp asc"` (default) and `"timestamp desc"`. The first option returns entries in * order of increasing values of `LogEntry.timestamp` (oldest first), and the second option @@ -399,8 +415,8 @@ public final ListLogEntriesPagedResponse listLogEntries( // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists log entries. Use this method to retrieve log entries from Cloud Logging. For ways to - * export log entries, see [Exporting Logs](/logging/docs/export). + * Lists log entries. Use this method to retrieve log entries from Stackdriver Logging. For ways + * to export log entries, see [Exporting Logs](/logging/docs/export). * *

    Sample code: * @@ -425,8 +441,8 @@ public final ListLogEntriesPagedResponse listLogEntries(ListLogEntriesRequest re // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists log entries. Use this method to retrieve log entries from Cloud Logging. For ways to - * export log entries, see [Exporting Logs](/logging/docs/export). + * Lists log entries. Use this method to retrieve log entries from Stackdriver Logging. For ways + * to export log entries, see [Exporting Logs](/logging/docs/export). * *

    Sample code: * @@ -451,8 +467,8 @@ public final ListLogEntriesPagedResponse listLogEntries(ListLogEntriesRequest re // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists log entries. Use this method to retrieve log entries from Cloud Logging. For ways to - * export log entries, see [Exporting Logs](/logging/docs/export). + * Lists log entries. Use this method to retrieve log entries from Stackdriver Logging. For ways + * to export log entries, see [Exporting Logs](/logging/docs/export). * *

    Sample code: * @@ -484,7 +500,7 @@ public final ListLogEntriesPagedResponse listLogEntries(ListLogEntriesRequest re // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists the monitored resource descriptors used by Stackdriver Logging. + * Lists the descriptors for monitored resource types used by Stackdriver Logging. * *

    Sample code: * @@ -507,7 +523,7 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists the monitored resource descriptors used by Stackdriver Logging. + * Lists the descriptors for monitored resource types used by Stackdriver Logging. * *

    Sample code: * @@ -530,7 +546,7 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource // AUTO-GENERATED DOCUMENTATION AND METHOD /** - * Lists the monitored resource descriptors used by Stackdriver Logging. + * Lists the descriptors for monitored resource types used by Stackdriver Logging. * *

    Sample code: * @@ -558,6 +574,111 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource return listMonitoredResourceDescriptorsCallable; } + // AUTO-GENERATED DOCUMENTATION AND METHOD + /** + * Lists the logs in projects or organizations. Only logs that have entries are listed. + * + *

    Sample code: + * + *

    
    +   * try (LoggingServiceV2Client loggingServiceV2Client = LoggingServiceV2Client.create()) {
    +   *   ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]"));
    +   *   for (String element : loggingServiceV2Client.listLogs(parent).iterateAllElements()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * 
    + * + * @param parent Required. The resource name that owns the logs: + *

    "projects/[PROJECT_ID]" "organizations/[ORGANIZATION_ID]" + * @throws com.google.api.gax.grpc.ApiException if the remote call fails + */ + public final ListLogsPagedResponse listLogs(ParentNameOneof parent) { + ListLogsRequest request = + ListLogsRequest.newBuilder().setParentWithParentNameOneof(parent).build(); + return listLogs(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD + /** + * Lists the logs in projects or organizations. Only logs that have entries are listed. + * + *

    Sample code: + * + *

    
    +   * try (LoggingServiceV2Client loggingServiceV2Client = LoggingServiceV2Client.create()) {
    +   *   ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]"));
    +   *   ListLogsRequest request = ListLogsRequest.newBuilder()
    +   *     .setParentWithParentNameOneof(parent)
    +   *     .build();
    +   *   for (String element : loggingServiceV2Client.listLogs(request).iterateAllElements()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * 
    + * + * @param request The request object containing all of the parameters for the API call. + * @throws com.google.api.gax.grpc.ApiException if the remote call fails + */ + public final ListLogsPagedResponse listLogs(ListLogsRequest request) { + return listLogsPagedCallable().call(request); + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD + /** + * Lists the logs in projects or organizations. Only logs that have entries are listed. + * + *

    Sample code: + * + *

    
    +   * try (LoggingServiceV2Client loggingServiceV2Client = LoggingServiceV2Client.create()) {
    +   *   ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]"));
    +   *   ListLogsRequest request = ListLogsRequest.newBuilder()
    +   *     .setParentWithParentNameOneof(parent)
    +   *     .build();
    +   *   ListenableFuture<ListLogsPagedResponse> future = loggingServiceV2Client.listLogsPagedCallable().futureCall(request);
    +   *   // Do something
    +   *   for (String element : future.get().iterateAllElements()) {
    +   *     // doThingsWith(element);
    +   *   }
    +   * }
    +   * 
    + */ + public final UnaryCallable listLogsPagedCallable() { + return listLogsPagedCallable; + } + + // AUTO-GENERATED DOCUMENTATION AND METHOD + /** + * Lists the logs in projects or organizations. Only logs that have entries are listed. + * + *

    Sample code: + * + *

    
    +   * try (LoggingServiceV2Client loggingServiceV2Client = LoggingServiceV2Client.create()) {
    +   *   ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]"));
    +   *   ListLogsRequest request = ListLogsRequest.newBuilder()
    +   *     .setParentWithParentNameOneof(parent)
    +   *     .build();
    +   *   while (true) {
    +   *     ListLogsResponse response = loggingServiceV2Client.listLogsCallable().call(request);
    +   *     for (String element : response.getLogNamesList()) {
    +   *       // doThingsWith(element);
    +   *     }
    +   *     String nextPageToken = response.getNextPageToken();
    +   *     if (!Strings.isNullOrEmpty(nextPageToken)) {
    +   *       request = request.toBuilder().setPageToken(nextPageToken).build();
    +   *     } else {
    +   *       break;
    +   *     }
    +   *   }
    +   * }
    +   * 
    + */ + public final UnaryCallable listLogsCallable() { + return listLogsCallable; + } + /** * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately * cancelled. diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Settings.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Settings.java index 3d643d08dbb4..d4803273cf43 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Settings.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Settings.java @@ -16,11 +16,15 @@ package com.google.cloud.logging.spi.v2; import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogEntriesPagedResponse; +import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogsPagedResponse; import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListMonitoredResourceDescriptorsPagedResponse; import com.google.api.MonitoredResourceDescriptor; import com.google.api.gax.core.GoogleCredentialsProvider; import com.google.api.gax.core.RetrySettings; +import com.google.api.gax.grpc.BundlingCallSettings; +import com.google.api.gax.grpc.BundlingDescriptor; +import com.google.api.gax.grpc.BundlingSettings; import com.google.api.gax.grpc.CallContext; import com.google.api.gax.grpc.ChannelProvider; import com.google.api.gax.grpc.ClientSettings; @@ -30,6 +34,7 @@ import com.google.api.gax.grpc.PagedCallSettings; import com.google.api.gax.grpc.PagedListDescriptor; import com.google.api.gax.grpc.PagedListResponseFactory; +import com.google.api.gax.grpc.RequestIssuer; import com.google.api.gax.grpc.SimpleCallSettings; import com.google.api.gax.grpc.UnaryCallSettings; import com.google.api.gax.grpc.UnaryCallable; @@ -41,6 +46,8 @@ import com.google.logging.v2.DeleteLogRequest; import com.google.logging.v2.ListLogEntriesRequest; import com.google.logging.v2.ListLogEntriesResponse; +import com.google.logging.v2.ListLogsRequest; +import com.google.logging.v2.ListLogsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse; import com.google.logging.v2.LogEntry; @@ -51,6 +58,9 @@ import com.google.protobuf.ExperimentalApi; import io.grpc.Status; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import javax.annotation.Generated; import org.joda.time.Duration; @@ -100,7 +110,7 @@ public class LoggingServiceV2Settings extends ClientSettings { .build(); private final SimpleCallSettings deleteLogSettings; - private final SimpleCallSettings + private final BundlingCallSettings writeLogEntriesSettings; private final PagedCallSettings< ListLogEntriesRequest, ListLogEntriesResponse, ListLogEntriesPagedResponse> @@ -109,6 +119,8 @@ public class LoggingServiceV2Settings extends ClientSettings { ListMonitoredResourceDescriptorsRequest, ListMonitoredResourceDescriptorsResponse, ListMonitoredResourceDescriptorsPagedResponse> listMonitoredResourceDescriptorsSettings; + private final PagedCallSettings + listLogsSettings; /** Returns the object with the settings used for calls to deleteLog. */ public SimpleCallSettings deleteLogSettings() { @@ -116,7 +128,7 @@ public SimpleCallSettings deleteLogSettings() { } /** Returns the object with the settings used for calls to writeLogEntries. */ - public SimpleCallSettings + public BundlingCallSettings writeLogEntriesSettings() { return writeLogEntriesSettings; } @@ -136,6 +148,12 @@ public SimpleCallSettings deleteLogSettings() { return listMonitoredResourceDescriptorsSettings; } + /** Returns the object with the settings used for calls to listLogs. */ + public PagedCallSettings + listLogsSettings() { + return listLogsSettings; + } + /** Returns a builder for the default ExecutorProvider for this service. */ public static InstantiatingExecutorProvider.Builder defaultExecutorProviderBuilder() { return InstantiatingExecutorProvider.newBuilder(); @@ -192,6 +210,7 @@ private LoggingServiceV2Settings(Builder settingsBuilder) throws IOException { listLogEntriesSettings = settingsBuilder.listLogEntriesSettings().build(); listMonitoredResourceDescriptorsSettings = settingsBuilder.listMonitoredResourceDescriptorsSettings().build(); + listLogsSettings = settingsBuilder.listLogsSettings().build(); } private static final PagedListDescriptor @@ -274,6 +293,40 @@ public Iterable extractResources( } }; + private static final PagedListDescriptor + LIST_LOGS_PAGE_STR_DESC = + new PagedListDescriptor() { + @Override + public Object emptyToken() { + return ""; + } + + @Override + public ListLogsRequest injectToken(ListLogsRequest payload, Object token) { + return ListLogsRequest.newBuilder(payload).setPageToken((String) token).build(); + } + + @Override + public ListLogsRequest injectPageSize(ListLogsRequest payload, int pageSize) { + return ListLogsRequest.newBuilder(payload).setPageSize(pageSize).build(); + } + + @Override + public Integer extractPageSize(ListLogsRequest payload) { + return payload.getPageSize(); + } + + @Override + public Object extractNextToken(ListLogsResponse payload) { + return payload.getNextPageToken(); + } + + @Override + public Iterable extractResources(ListLogsResponse payload) { + return payload.getLogNamesList(); + } + }; + private static final PagedListResponseFactory< ListLogEntriesRequest, ListLogEntriesResponse, ListLogEntriesPagedResponse> LIST_LOG_ENTRIES_PAGE_STR_FACT = @@ -309,12 +362,93 @@ public ListMonitoredResourceDescriptorsPagedResponse createPagedListResponse( } }; + private static final PagedListResponseFactory< + ListLogsRequest, ListLogsResponse, ListLogsPagedResponse> + LIST_LOGS_PAGE_STR_FACT = + new PagedListResponseFactory() { + @Override + public ListLogsPagedResponse createPagedListResponse( + UnaryCallable callable, + ListLogsRequest request, + CallContext context) { + return new ListLogsPagedResponse(callable, LIST_LOGS_PAGE_STR_DESC, request, context); + } + }; + + private static final BundlingDescriptor + WRITE_LOG_ENTRIES_BUNDLING_DESC = + new BundlingDescriptor() { + @Override + public String getBundlePartitionKey(WriteLogEntriesRequest request) { + return request.getLogName() + + "|" + + request.getResource() + + "|" + + request.getLabels() + + "|"; + } + + @Override + public WriteLogEntriesRequest mergeRequests( + Collection requests) { + WriteLogEntriesRequest firstRequest = requests.iterator().next(); + + List elements = new ArrayList<>(); + for (WriteLogEntriesRequest request : requests) { + elements.addAll(request.getEntriesList()); + } + + WriteLogEntriesRequest bundleRequest = + WriteLogEntriesRequest.newBuilder() + .setLogName(firstRequest.getLogName()) + .setResource(firstRequest.getResource()) + .putAllLabels(firstRequest.getLabels()) + .addAllEntries(elements) + .build(); + return bundleRequest; + } + + @Override + public void splitResponse( + WriteLogEntriesResponse bundleResponse, + Collection> + bundle) { + int bundleMessageIndex = 0; + for (RequestIssuer responder : + bundle) { + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + responder.setResponse(response); + } + } + + @Override + public void splitException( + Throwable throwable, + Collection> + bundle) { + for (RequestIssuer responder : + bundle) { + responder.setException(throwable); + } + } + + @Override + public long countElements(WriteLogEntriesRequest request) { + return request.getEntriesCount(); + } + + @Override + public long countBytes(WriteLogEntriesRequest request) { + return request.getSerializedSize(); + } + }; + /** Builder for LoggingServiceV2Settings. */ public static class Builder extends ClientSettings.Builder { private final ImmutableList unaryMethodSettingsBuilders; private final SimpleCallSettings.Builder deleteLogSettings; - private final SimpleCallSettings.Builder + private final BundlingCallSettings.Builder writeLogEntriesSettings; private final PagedCallSettings.Builder< ListLogEntriesRequest, ListLogEntriesResponse, ListLogEntriesPagedResponse> @@ -323,6 +457,9 @@ public static class Builder extends ClientSettings.Builder { ListMonitoredResourceDescriptorsRequest, ListMonitoredResourceDescriptorsResponse, ListMonitoredResourceDescriptorsPagedResponse> listMonitoredResourceDescriptorsSettings; + private final PagedCallSettings.Builder< + ListLogsRequest, ListLogsResponse, ListLogsPagedResponse> + listLogsSettings; private static final ImmutableMap> RETRYABLE_CODE_DEFINITIONS; @@ -371,7 +508,9 @@ private Builder() { deleteLogSettings = SimpleCallSettings.newBuilder(LoggingServiceV2Grpc.METHOD_DELETE_LOG); writeLogEntriesSettings = - SimpleCallSettings.newBuilder(LoggingServiceV2Grpc.METHOD_WRITE_LOG_ENTRIES); + BundlingCallSettings.newBuilder( + LoggingServiceV2Grpc.METHOD_WRITE_LOG_ENTRIES, WRITE_LOG_ENTRIES_BUNDLING_DESC) + .setBundlingSettingsBuilder(BundlingSettings.newBuilder()); listLogEntriesSettings = PagedCallSettings.newBuilder( @@ -382,12 +521,17 @@ private Builder() { LoggingServiceV2Grpc.METHOD_LIST_MONITORED_RESOURCE_DESCRIPTORS, LIST_MONITORED_RESOURCE_DESCRIPTORS_PAGE_STR_FACT); + listLogsSettings = + PagedCallSettings.newBuilder( + LoggingServiceV2Grpc.METHOD_LIST_LOGS, LIST_LOGS_PAGE_STR_FACT); + unaryMethodSettingsBuilders = ImmutableList.of( deleteLogSettings, writeLogEntriesSettings, listLogEntriesSettings, - listMonitoredResourceDescriptorsSettings); + listMonitoredResourceDescriptorsSettings, + listLogsSettings); } private static Builder createDefault() { @@ -398,6 +542,12 @@ private static Builder createDefault() { .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("idempotent")) .setRetrySettingsBuilder(RETRY_PARAM_DEFINITIONS.get("default")); + builder + .writeLogEntriesSettings() + .getBundlingSettingsBuilder() + .setElementCountThreshold(1) + .setRequestByteThreshold(1024) + .setDelayThreshold(Duration.millis(10)); builder .writeLogEntriesSettings() .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("non_idempotent")) @@ -413,6 +563,11 @@ private static Builder createDefault() { .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("idempotent")) .setRetrySettingsBuilder(RETRY_PARAM_DEFINITIONS.get("default")); + builder + .listLogsSettings() + .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("idempotent")) + .setRetrySettingsBuilder(RETRY_PARAM_DEFINITIONS.get("default")); + return builder; } @@ -424,13 +579,15 @@ private Builder(LoggingServiceV2Settings settings) { listLogEntriesSettings = settings.listLogEntriesSettings.toBuilder(); listMonitoredResourceDescriptorsSettings = settings.listMonitoredResourceDescriptorsSettings.toBuilder(); + listLogsSettings = settings.listLogsSettings.toBuilder(); unaryMethodSettingsBuilders = ImmutableList.of( deleteLogSettings, writeLogEntriesSettings, listLogEntriesSettings, - listMonitoredResourceDescriptorsSettings); + listMonitoredResourceDescriptorsSettings, + listLogsSettings); } @Override @@ -463,7 +620,7 @@ public SimpleCallSettings.Builder deleteLogSettings() { } /** Returns the builder for the settings used for calls to writeLogEntries. */ - public SimpleCallSettings.Builder + public BundlingCallSettings.Builder writeLogEntriesSettings() { return writeLogEntriesSettings; } @@ -483,6 +640,12 @@ public SimpleCallSettings.Builder deleteLogSettings() { return listMonitoredResourceDescriptorsSettings; } + /** Returns the builder for the settings used for calls to listLogs. */ + public PagedCallSettings.Builder + listLogsSettings() { + return listLogsSettings; + } + @Override public LoggingServiceV2Settings build() throws IOException { return new LoggingServiceV2Settings(this); diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/PagedResponseWrappers.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/PagedResponseWrappers.java index 60e880bca655..30ac77a1cd25 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/PagedResponseWrappers.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/PagedResponseWrappers.java @@ -24,6 +24,8 @@ import com.google.logging.v2.ListLogEntriesResponse; import com.google.logging.v2.ListLogMetricsRequest; import com.google.logging.v2.ListLogMetricsResponse; +import com.google.logging.v2.ListLogsRequest; +import com.google.logging.v2.ListLogsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse; import com.google.logging.v2.ListSinksRequest; @@ -75,6 +77,18 @@ public ListMonitoredResourceDescriptorsPagedResponse( } } + public static class ListLogsPagedResponse + extends PagedListResponseImpl { + + public ListLogsPagedResponse( + UnaryCallable callable, + PagedListDescriptor pageDescriptor, + ListLogsRequest request, + CallContext context) { + super(callable, pageDescriptor, request, context); + } + } + public static class ListSinksPagedResponse extends PagedListResponseImpl { diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java index 495430c23d75..8ea02628309f 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java @@ -65,6 +65,7 @@ public void testPublish() { .setSeverity(Severity.DEBUG) .addLabel("levelName", "FINEST") .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .setTimestamp(123456789L) .build(); EasyMock.expect(logging.writeAsync(ImmutableList.of(entry), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE))).andReturn(FUTURE); @@ -72,6 +73,8 @@ public void testPublish() { Handler handler = new AsyncLoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); + LogRecord record = new LogRecord(Level.FINEST, MESSAGE); + record.setMillis(123456789L); + handler.publish(record); } } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java index e75c981376f9..7b4f3298c1d4 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java @@ -16,28 +16,25 @@ package com.google.cloud.logging; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - import com.google.cloud.MonitoredResource; +import com.google.cloud.logging.LogEntry.Builder; import com.google.cloud.logging.Logging.WriteOption; import com.google.cloud.logging.Payload.StringPayload; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; -import org.easymock.EasyMock; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - +import java.util.Collections; import java.util.logging.ErrorManager; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; public class LoggingHandlerTest { @@ -50,66 +47,86 @@ public class LoggingHandlerTest { .setSeverity(Severity.DEBUG) .addLabel("levelName", "FINEST") .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .setTimestamp(123456789L) + .build(); + private static final LogEntry FINEST_ENHANCED_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) + .setSeverity(Severity.DEBUG) + .addLabel("levelName", "FINEST") + .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .addLabel("enhanced", "true") + .setTimestamp(123456789L) .build(); private static final LogEntry FINER_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.DEBUG) .addLabel("levelName", "FINER") .addLabel("levelValue", String.valueOf(Level.FINER.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry FINE_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.DEBUG) .addLabel("levelName", "FINE") .addLabel("levelValue", String.valueOf(Level.FINE.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry CONFIG_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.INFO) .addLabel("levelName", "CONFIG") .addLabel("levelValue", String.valueOf(Level.CONFIG.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry INFO_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.INFO) .addLabel("levelName", "INFO") .addLabel("levelValue", String.valueOf(Level.INFO.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry WARNING_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.WARNING) .addLabel("levelName", "WARNING") .addLabel("levelValue", String.valueOf(Level.WARNING.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry SEVERE_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.ERROR) .addLabel("levelName", "SEVERE") .addLabel("levelValue", String.valueOf(Level.SEVERE.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry DEBUG_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.DEBUG) .addLabel("levelName", "DEBUG") .addLabel("levelValue", String.valueOf(LoggingLevel.DEBUG.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry NOTICE_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.NOTICE) .addLabel("levelName", "NOTICE") .addLabel("levelValue", String.valueOf(LoggingLevel.NOTICE.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry ERROR_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.ERROR) .addLabel("levelName", "ERROR") .addLabel("levelValue", String.valueOf(LoggingLevel.ERROR.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry CRITICAL_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.CRITICAL) .addLabel("levelName", "CRITICAL") .addLabel("levelValue", String.valueOf(LoggingLevel.CRITICAL.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry ALERT_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.ALERT) .addLabel("levelName", "ALERT") .addLabel("levelValue", String.valueOf(LoggingLevel.ALERT.intValue())) + .setTimestamp(123456789L) .build(); private static final LogEntry EMERGENCY_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.EMERGENCY) .addLabel("levelName", "EMERGENCY") .addLabel("levelValue", String.valueOf(LoggingLevel.EMERGENCY.intValue())) + .setTimestamp(123456789L) .build(); private Logging logging; @@ -133,69 +150,76 @@ public void setUp() { public void afterClass() { EasyMock.verify(logging, options); } + + + private static LogRecord newLogRecord(Level level, String message) { + LogRecord record = new LogRecord(level, message); + record.setMillis(123456789L); + return record; + } @Test public void testPublishLevels() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); - logging.write(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), + logging.writeAsync(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(FINER_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(FINER_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(FINE_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(FINE_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(CONFIG_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(CONFIG_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(INFO_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(INFO_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(WARNING_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(WARNING_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(SEVERE_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(SEVERE_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(DEBUG_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(DEBUG_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(NOTICE_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(NOTICE_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(ERROR_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(ERROR_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(CRITICAL_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(CRITICAL_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(ALERT_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(ALERT_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); - logging.write(ImmutableList.of(EMERGENCY_ENTRY), WriteOption.logName(LOG_NAME), + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + logging.writeAsync(ImmutableList.of(EMERGENCY_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); EasyMock.replay(options, logging); Handler handler = new LoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setFormatter(new TestFormatter()); // default levels - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); - handler.publish(new LogRecord(Level.FINER, MESSAGE)); - handler.publish(new LogRecord(Level.FINE, MESSAGE)); - handler.publish(new LogRecord(Level.CONFIG, MESSAGE)); - handler.publish(new LogRecord(Level.INFO, MESSAGE)); - handler.publish(new LogRecord(Level.WARNING, MESSAGE)); - handler.publish(new LogRecord(Level.SEVERE, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINER, MESSAGE)); + handler.publish(newLogRecord(Level.FINE, MESSAGE)); + handler.publish(newLogRecord(Level.CONFIG, MESSAGE)); + handler.publish(newLogRecord(Level.INFO, MESSAGE)); + handler.publish(newLogRecord(Level.WARNING, MESSAGE)); + handler.publish(newLogRecord(Level.SEVERE, MESSAGE)); // Logging levels - handler.publish(new LogRecord(LoggingLevel.DEBUG, MESSAGE)); - handler.publish(new LogRecord(LoggingLevel.NOTICE, MESSAGE)); - handler.publish(new LogRecord(LoggingLevel.ERROR, MESSAGE)); - handler.publish(new LogRecord(LoggingLevel.CRITICAL, MESSAGE)); - handler.publish(new LogRecord(LoggingLevel.ALERT, MESSAGE)); - handler.publish(new LogRecord(LoggingLevel.EMERGENCY, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.DEBUG, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.NOTICE, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.ERROR, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.CRITICAL, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.ALERT, MESSAGE)); + handler.publish(newLogRecord(LoggingLevel.EMERGENCY, MESSAGE)); } @Test @@ -203,34 +227,61 @@ public void testPublishCustomResource() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); MonitoredResource resource = MonitoredResource.of("custom", ImmutableMap.of()); - logging.write(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), + logging.writeAsync(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(resource)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); EasyMock.replay(options, logging); Handler handler = new LoggingHandler(LOG_NAME, options, resource); handler.setLevel(Level.ALL); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); } + @Test + public void testEnhancedLogEntry() { + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(options.getService()).andReturn(logging); + MonitoredResource resource = MonitoredResource.of("custom", ImmutableMap.of()); + logging.writeAsync(ImmutableList.of(FINEST_ENHANCED_ENTRY), WriteOption.logName(LOG_NAME), + WriteOption.resource(resource)); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + EasyMock.replay(options, logging); + LoggingHandler.Enhancer enhancer = new LoggingHandler.Enhancer() { + @Override + public void enhanceMonitoredResource(MonitoredResource.Builder builder) { + throw new IllegalStateException(); + } + + @Override + public void enhanceLogEntry(Builder builder, LogRecord record) { + builder.addLabel("enhanced", "true"); + } + }; + Handler handler = + new LoggingHandler(LOG_NAME, options, resource, Collections.singletonList(enhancer)); + handler.setLevel(Level.ALL); + handler.setFormatter(new TestFormatter()); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + } + @Test public void testReportFlushError() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); RuntimeException ex = new RuntimeException(); - logging.write(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), + logging.writeAsync(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); EasyMock.expectLastCall().andThrow(ex); EasyMock.replay(options, logging); ErrorManager errorManager = EasyMock.createStrictMock(ErrorManager.class); errorManager.error(null, ex, ErrorManager.FLUSH_FAILURE); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().once(); EasyMock.replay(errorManager); Handler handler = new LoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setErrorManager(errorManager); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); EasyMock.verify(errorManager); } @@ -242,8 +293,8 @@ public void testReportFormatError() { RuntimeException ex = new RuntimeException(); ErrorManager errorManager = EasyMock.createStrictMock(ErrorManager.class); errorManager.error(null, ex, ErrorManager.FORMAT_FAILURE); - EasyMock.expectLastCall(); - LogRecord record = new LogRecord(Level.FINEST, MESSAGE); + EasyMock.expectLastCall().once(); + LogRecord record = newLogRecord(Level.FINEST, MESSAGE); EasyMock.expect(formatter.format(record)).andThrow(ex); EasyMock.replay(errorManager, formatter); Handler handler = new LoggingHandler(LOG_NAME, options); @@ -258,91 +309,80 @@ public void testReportFormatError() { public void testFlushSize() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); - logging.write(ImmutableList.of(FINEST_ENTRY, FINER_ENTRY, FINE_ENTRY, CONFIG_ENTRY, INFO_ENTRY, + logging.writeAsync(ImmutableList.of(FINEST_ENTRY, FINER_ENTRY, FINE_ENTRY, CONFIG_ENTRY, INFO_ENTRY, WARNING_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); EasyMock.replay(options, logging); LoggingHandler handler = new LoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setFlushSize(6); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); - handler.publish(new LogRecord(Level.FINER, MESSAGE)); - handler.publish(new LogRecord(Level.FINE, MESSAGE)); - handler.publish(new LogRecord(Level.CONFIG, MESSAGE)); - handler.publish(new LogRecord(Level.INFO, MESSAGE)); - handler.publish(new LogRecord(Level.WARNING, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINER, MESSAGE)); + handler.publish(newLogRecord(Level.FINE, MESSAGE)); + handler.publish(newLogRecord(Level.CONFIG, MESSAGE)); + handler.publish(newLogRecord(Level.INFO, MESSAGE)); + handler.publish(newLogRecord(Level.WARNING, MESSAGE)); } @Test public void testFlushLevel() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); - logging.write(ImmutableList.of(FINEST_ENTRY, FINER_ENTRY, FINE_ENTRY, CONFIG_ENTRY, INFO_ENTRY, + logging.writeAsync(ImmutableList.of(FINEST_ENTRY, FINER_ENTRY, FINE_ENTRY, CONFIG_ENTRY, INFO_ENTRY, WARNING_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); EasyMock.replay(options, logging); LoggingHandler handler = new LoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setFlushSize(100); handler.setFlushLevel(Level.WARNING); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); - handler.publish(new LogRecord(Level.FINER, MESSAGE)); - handler.publish(new LogRecord(Level.FINE, MESSAGE)); - handler.publish(new LogRecord(Level.CONFIG, MESSAGE)); - handler.publish(new LogRecord(Level.INFO, MESSAGE)); - handler.publish(new LogRecord(Level.WARNING, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINER, MESSAGE)); + handler.publish(newLogRecord(Level.FINE, MESSAGE)); + handler.publish(newLogRecord(Level.CONFIG, MESSAGE)); + handler.publish(newLogRecord(Level.INFO, MESSAGE)); + handler.publish(newLogRecord(Level.WARNING, MESSAGE)); } @Test public void testAddHandler() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); - logging.write(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), + logging.writeAsync(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); EasyMock.replay(options, logging); - LoggingHandler handler = new LoggingHandler(LOG_NAME, options); + LoggingHandler handler = new LoggingHandler(LOG_NAME, options) { + @Override + public void close() { + // Make close NOOP to avoid mock close exception + } + }; handler.setLevel(Level.ALL); handler.setFormatter(new TestFormatter()); Logger logger = Logger.getLogger(getClass().getName()); logger.setLevel(Level.ALL); LoggingHandler.addHandler(logger, handler); - logger.finest(MESSAGE); - } - - @Test - public void testMaskLoggers() { - EasyMock.expect(options.getProjectId()).andReturn(PROJECT); - EasyMock.replay(options, logging); - LoggingHandler handler = new LoggingHandler(LOG_NAME, options); - Logger logger = Logger.getLogger("com.google"); - Logger maskedLogger = Logger.getLogger("com.google.api.client.http"); - maskedLogger.addHandler(handler); - assertTrue(maskedLogger.getUseParentHandlers()); - assertSame(logger, maskedLogger.getParent()); - LoggingHandler.addHandler(logger, handler); - assertFalse(maskedLogger.getUseParentHandlers()); - assertEquals(0, maskedLogger.getHandlers().length); - logger.removeHandler(handler); + logger.log(newLogRecord(Level.FINEST, MESSAGE)); } @Test public void testClose() throws Exception { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); EasyMock.expect(options.getService()).andReturn(logging); - logging.write(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), + logging.writeAsync(ImmutableList.of(FINEST_ENTRY), WriteOption.logName(LOG_NAME), WriteOption.resource(DEFAULT_RESOURCE)); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); logging.close(); - EasyMock.expectLastCall(); + EasyMock.expectLastCall().once(); EasyMock.replay(options, logging); Handler handler = new LoggingHandler(LOG_NAME, options); handler.setLevel(Level.ALL); handler.setFormatter(new TestFormatter()); - handler.publish(new LogRecord(Level.FINEST, MESSAGE)); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); handler.close(); handler.close(); } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Test.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Test.java index c12e838da738..a2f10062c3bb 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Test.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/LoggingServiceV2Test.java @@ -16,6 +16,7 @@ package com.google.cloud.logging.spi.v2; import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogEntriesPagedResponse; +import static com.google.cloud.logging.spi.v2.PagedResponseWrappers.ListLogsPagedResponse; import com.google.api.MonitoredResource; import com.google.api.gax.grpc.ApiException; @@ -25,9 +26,13 @@ import com.google.logging.v2.DeleteLogRequest; import com.google.logging.v2.ListLogEntriesRequest; import com.google.logging.v2.ListLogEntriesResponse; +import com.google.logging.v2.ListLogsRequest; +import com.google.logging.v2.ListLogsResponse; import com.google.logging.v2.LogEntry; import com.google.logging.v2.LogName; import com.google.logging.v2.LogNameOneof; +import com.google.logging.v2.ParentNameOneof; +import com.google.logging.v2.ProjectName; import com.google.logging.v2.WriteLogEntriesRequest; import com.google.logging.v2.WriteLogEntriesResponse; import com.google.protobuf.Empty; @@ -215,4 +220,48 @@ public void listLogEntriesExceptionTest() throws Exception { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); } } + + @Test + @SuppressWarnings("all") + public void listLogsTest() { + String nextPageToken = ""; + String logNamesElement = "logNamesElement-1079688374"; + List logNames = Arrays.asList(logNamesElement); + ListLogsResponse expectedResponse = + ListLogsResponse.newBuilder() + .setNextPageToken(nextPageToken) + .addAllLogNames(logNames) + .build(); + mockLoggingServiceV2.addResponse(expectedResponse); + + ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]")); + + ListLogsPagedResponse pagedListResponse = client.listLogs(parent); + + List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); + Assert.assertEquals(1, resources.size()); + Assert.assertEquals(expectedResponse.getLogNamesList().get(0), resources.get(0)); + + List actualRequests = mockLoggingServiceV2.getRequests(); + Assert.assertEquals(1, actualRequests.size()); + ListLogsRequest actualRequest = (ListLogsRequest) actualRequests.get(0); + + Assert.assertEquals(parent, actualRequest.getParentAsParentNameOneof()); + } + + @Test + @SuppressWarnings("all") + public void listLogsExceptionTest() throws Exception { + StatusRuntimeException exception = new StatusRuntimeException(Status.INTERNAL); + mockLoggingServiceV2.addException(exception); + + try { + ParentNameOneof parent = ParentNameOneof.from(ProjectName.create("[PROJECT]")); + + client.listLogs(parent); + Assert.fail("No exception raised"); + } catch (ApiException e) { + Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); + } + } } diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/MockLoggingServiceV2Impl.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/MockLoggingServiceV2Impl.java index c6775b8908bb..02090c9934d4 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/MockLoggingServiceV2Impl.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/spi/v2/MockLoggingServiceV2Impl.java @@ -18,6 +18,8 @@ import com.google.logging.v2.DeleteLogRequest; import com.google.logging.v2.ListLogEntriesRequest; import com.google.logging.v2.ListLogEntriesResponse; +import com.google.logging.v2.ListLogsRequest; +import com.google.logging.v2.ListLogsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse; import com.google.logging.v2.LoggingServiceV2Grpc.LoggingServiceV2ImplBase; @@ -121,4 +123,18 @@ public void listMonitoredResourceDescriptors( responseObserver.onError(new IllegalArgumentException("Unrecognized response type")); } } + + @Override + public void listLogs(ListLogsRequest request, StreamObserver responseObserver) { + Object response = responses.remove(); + if (response instanceof ListLogsResponse) { + requests.add(request); + responseObserver.onNext((ListLogsResponse) response); + responseObserver.onCompleted(); + } else if (response instanceof Exception) { + responseObserver.onError((Exception) response); + } else { + responseObserver.onError(new IllegalArgumentException("Unrecognized response type")); + } + } } diff --git a/google-cloud-monitoring/pom.xml b/google-cloud-monitoring/pom.xml index be83a529ed1b..e4b6a969e85c 100644 --- a/google-cloud-monitoring/pom.xml +++ b/google-cloud-monitoring/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-monitoring @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-monitoring-v3 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/GroupServiceClient.java b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/GroupServiceClient.java index d1f05562d37b..c927f6dc3ee2 100644 --- a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/GroupServiceClient.java +++ b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/GroupServiceClient.java @@ -20,15 +20,16 @@ import com.google.api.gax.grpc.ChannelAndExecutor; import com.google.api.gax.grpc.UnaryCallable; -import com.google.api.gax.protobuf.PathTemplate; import com.google.monitoring.v3.CreateGroupRequest; import com.google.monitoring.v3.DeleteGroupRequest; import com.google.monitoring.v3.GetGroupRequest; import com.google.monitoring.v3.Group; +import com.google.monitoring.v3.GroupName; import com.google.monitoring.v3.ListGroupMembersRequest; import com.google.monitoring.v3.ListGroupMembersResponse; import com.google.monitoring.v3.ListGroupsRequest; import com.google.monitoring.v3.ListGroupsResponse; +import com.google.monitoring.v3.ProjectName; import com.google.monitoring.v3.UpdateGroupRequest; import com.google.protobuf.Empty; import com.google.protobuf.ExperimentalApi; @@ -58,8 +59,8 @@ *
      * 
      * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    - *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    - *   Group response = groupServiceClient.getGroup(formattedName);
    + *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
    + *   Group response = groupServiceClient.getGroup(name);
      * }
      * 
      * 
    @@ -122,39 +123,6 @@ public class GroupServiceClient implements AutoCloseable { private final UnaryCallable listGroupMembersPagedCallable; - private static final PathTemplate PROJECT_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}"); - - private static final PathTemplate GROUP_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}/groups/{group}"); - - /** Formats a string containing the fully-qualified path to represent a project resource. */ - public static final String formatProjectName(String project) { - return PROJECT_PATH_TEMPLATE.instantiate("project", project); - } - - /** Formats a string containing the fully-qualified path to represent a group resource. */ - public static final String formatGroupName(String project, String group) { - return GROUP_PATH_TEMPLATE.instantiate( - "project", project, - "group", group); - } - - /** Parses the project from the given fully-qualified path which represents a project resource. */ - public static final String parseProjectFromProjectName(String projectName) { - return PROJECT_PATH_TEMPLATE.parse(projectName).get("project"); - } - - /** Parses the project from the given fully-qualified path which represents a group resource. */ - public static final String parseProjectFromGroupName(String groupName) { - return GROUP_PATH_TEMPLATE.parse(groupName).get("project"); - } - - /** Parses the group from the given fully-qualified path which represents a group resource. */ - public static final String parseGroupFromGroupName(String groupName) { - return GROUP_PATH_TEMPLATE.parse(groupName).get("group"); - } - /** Constructs an instance of GroupServiceClient with default settings. */ public static final GroupServiceClient create() throws IOException { return create(GroupServiceSettings.defaultBuilder().build()); @@ -229,9 +197,9 @@ public final GroupServiceSettings getSettings() { * *
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListGroupsRequest request = ListGroupsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   for (Group element : groupServiceClient.listGroups(request).iterateAllElements()) {
        *     // doThingsWith(element);
    @@ -254,9 +222,9 @@ public final ListGroupsPagedResponse listGroups(ListGroupsRequest request) {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListGroupsRequest request = ListGroupsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   ListenableFuture<ListGroupsPagedResponse> future = groupServiceClient.listGroupsPagedCallable().futureCall(request);
        *   // Do something
    @@ -278,9 +246,9 @@ public final UnaryCallable listGroup
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListGroupsRequest request = ListGroupsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   while (true) {
        *     ListGroupsResponse response = groupServiceClient.listGroupsCallable().call(request);
    @@ -309,8 +277,8 @@ public final UnaryCallable listGroupsCall
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    -   *   Group response = groupServiceClient.getGroup(formattedName);
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
    +   *   Group response = groupServiceClient.getGroup(name);
        * }
        * 
    * @@ -318,9 +286,9 @@ public final UnaryCallable listGroupsCall * `"projects/{project_id_or_number}/groups/{group_id}"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final Group getGroup(String name) { - GROUP_PATH_TEMPLATE.validate(name, "getGroup"); - GetGroupRequest request = GetGroupRequest.newBuilder().setName(name).build(); + public final Group getGroup(GroupName name) { + + GetGroupRequest request = GetGroupRequest.newBuilder().setNameWithGroupName(name).build(); return getGroup(request); } @@ -332,9 +300,9 @@ public final Group getGroup(String name) { * *
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   GetGroupRequest request = GetGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   Group response = groupServiceClient.getGroup(request);
        * }
    @@ -355,9 +323,9 @@ private final Group getGroup(GetGroupRequest request) {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   GetGroupRequest request = GetGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   ListenableFuture<Group> future = groupServiceClient.getGroupCallable().futureCall(request);
        *   // Do something
    @@ -377,9 +345,9 @@ public final UnaryCallable getGroupCallable() {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   Group group = Group.newBuilder().build();
    -   *   Group response = groupServiceClient.createGroup(formattedName, group);
    +   *   Group response = groupServiceClient.createGroup(name, group);
        * }
        * 
    * @@ -389,10 +357,10 @@ public final UnaryCallable getGroupCallable() { * assigns the name. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final Group createGroup(String name, Group group) { - PROJECT_PATH_TEMPLATE.validate(name, "createGroup"); + public final Group createGroup(ProjectName name, Group group) { + CreateGroupRequest request = - CreateGroupRequest.newBuilder().setName(name).setGroup(group).build(); + CreateGroupRequest.newBuilder().setNameWithProjectName(name).setGroup(group).build(); return createGroup(request); } @@ -404,10 +372,10 @@ public final Group createGroup(String name, Group group) { * *
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   Group group = Group.newBuilder().build();
        *   CreateGroupRequest request = CreateGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setGroup(group)
        *     .build();
        *   Group response = groupServiceClient.createGroup(request);
    @@ -429,10 +397,10 @@ public final Group createGroup(CreateGroupRequest request) {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   Group group = Group.newBuilder().build();
        *   CreateGroupRequest request = CreateGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setGroup(group)
        *     .build();
        *   ListenableFuture<Group> future = groupServiceClient.createGroupCallable().futureCall(request);
    @@ -521,8 +489,8 @@ public final UnaryCallable updateGroupCallable() {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    -   *   groupServiceClient.deleteGroup(formattedName);
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
    +   *   groupServiceClient.deleteGroup(name);
        * }
        * 
    * @@ -530,9 +498,9 @@ public final UnaryCallable updateGroupCallable() { * `"projects/{project_id_or_number}/groups/{group_id}"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final void deleteGroup(String name) { - GROUP_PATH_TEMPLATE.validate(name, "deleteGroup"); - DeleteGroupRequest request = DeleteGroupRequest.newBuilder().setName(name).build(); + public final void deleteGroup(GroupName name) { + + DeleteGroupRequest request = DeleteGroupRequest.newBuilder().setNameWithGroupName(name).build(); deleteGroup(request); } @@ -544,9 +512,9 @@ public final void deleteGroup(String name) { * *
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   DeleteGroupRequest request = DeleteGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   groupServiceClient.deleteGroup(request);
        * }
    @@ -567,9 +535,9 @@ private final void deleteGroup(DeleteGroupRequest request) {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   DeleteGroupRequest request = DeleteGroupRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   ListenableFuture<Void> future = groupServiceClient.deleteGroupCallable().futureCall(request);
        *   // Do something
    @@ -589,8 +557,8 @@ public final UnaryCallable deleteGroupCallable() {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    -   *   for (MonitoredResource element : groupServiceClient.listGroupMembers(formattedName).iterateAllElements()) {
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
    +   *   for (MonitoredResource element : groupServiceClient.listGroupMembers(name).iterateAllElements()) {
        *     // doThingsWith(element);
        *   }
        * }
    @@ -600,9 +568,9 @@ public final UnaryCallable deleteGroupCallable() {
        *     `"projects/{project_id_or_number}/groups/{group_id}"`.
        * @throws com.google.api.gax.grpc.ApiException if the remote call fails
        */
    -  public final ListGroupMembersPagedResponse listGroupMembers(String name) {
    -    GROUP_PATH_TEMPLATE.validate(name, "listGroupMembers");
    -    ListGroupMembersRequest request = ListGroupMembersRequest.newBuilder().setName(name).build();
    +  public final ListGroupMembersPagedResponse listGroupMembers(GroupName name) {
    +    ListGroupMembersRequest request =
    +        ListGroupMembersRequest.newBuilder().setNameWithGroupName(name).build();
         return listGroupMembers(request);
       }
     
    @@ -614,9 +582,9 @@ public final ListGroupMembersPagedResponse listGroupMembers(String name) {
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   ListGroupMembersRequest request = ListGroupMembersRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   for (MonitoredResource element : groupServiceClient.listGroupMembers(request).iterateAllElements()) {
        *     // doThingsWith(element);
    @@ -639,9 +607,9 @@ public final ListGroupMembersPagedResponse listGroupMembers(ListGroupMembersRequ
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   ListGroupMembersRequest request = ListGroupMembersRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   ListenableFuture<ListGroupMembersPagedResponse> future = groupServiceClient.listGroupMembersPagedCallable().futureCall(request);
        *   // Do something
    @@ -664,9 +632,9 @@ public final ListGroupMembersPagedResponse listGroupMembers(ListGroupMembersRequ
        *
        * 
    
        * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    -   *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    +   *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
        *   ListGroupMembersRequest request = ListGroupMembersRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithGroupName(name)
        *     .build();
        *   while (true) {
        *     ListGroupMembersResponse response = groupServiceClient.listGroupMembersCallable().call(request);
    diff --git a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/MetricServiceClient.java b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/MetricServiceClient.java
    index a3c34aec881a..7dbe8909633d 100644
    --- a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/MetricServiceClient.java
    +++ b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/MetricServiceClient.java
    @@ -23,7 +23,6 @@
     import com.google.api.MonitoredResourceDescriptor;
     import com.google.api.gax.grpc.ChannelAndExecutor;
     import com.google.api.gax.grpc.UnaryCallable;
    -import com.google.api.gax.protobuf.PathTemplate;
     import com.google.monitoring.v3.CreateMetricDescriptorRequest;
     import com.google.monitoring.v3.CreateTimeSeriesRequest;
     import com.google.monitoring.v3.DeleteMetricDescriptorRequest;
    @@ -36,6 +35,9 @@
     import com.google.monitoring.v3.ListTimeSeriesRequest;
     import com.google.monitoring.v3.ListTimeSeriesRequest.TimeSeriesView;
     import com.google.monitoring.v3.ListTimeSeriesResponse;
    +import com.google.monitoring.v3.MetricDescriptorName;
    +import com.google.monitoring.v3.MonitoredResourceDescriptorName;
    +import com.google.monitoring.v3.ProjectName;
     import com.google.monitoring.v3.TimeInterval;
     import com.google.monitoring.v3.TimeSeries;
     import com.google.protobuf.Empty;
    @@ -59,8 +61,8 @@
      * 
      * 
      * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    - *   String formattedName = MetricServiceClient.formatMonitoredResourceDescriptorName("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    - *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(formattedName);
    + *   MonitoredResourceDescriptorName name = MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    + *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(name);
      * }
      * 
      * 
    @@ -134,86 +136,6 @@ public class MetricServiceClient implements AutoCloseable { listTimeSeriesPagedCallable; private final UnaryCallable createTimeSeriesCallable; - private static final PathTemplate PROJECT_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding("projects/{project}"); - - private static final PathTemplate METRIC_DESCRIPTOR_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding( - "projects/{project}/metricDescriptors/{metric_descriptor=**}"); - - private static final PathTemplate MONITORED_RESOURCE_DESCRIPTOR_PATH_TEMPLATE = - PathTemplate.createWithoutUrlEncoding( - "projects/{project}/monitoredResourceDescriptors/{monitored_resource_descriptor}"); - - /** Formats a string containing the fully-qualified path to represent a project resource. */ - public static final String formatProjectName(String project) { - return PROJECT_PATH_TEMPLATE.instantiate("project", project); - } - - /** - * Formats a string containing the fully-qualified path to represent a metric_descriptor resource. - */ - public static final String formatMetricDescriptorName(String project, String metricDescriptor) { - return METRIC_DESCRIPTOR_PATH_TEMPLATE.instantiate( - "project", project, - "metric_descriptor", metricDescriptor); - } - - /** - * Formats a string containing the fully-qualified path to represent a - * monitored_resource_descriptor resource. - */ - public static final String formatMonitoredResourceDescriptorName( - String project, String monitoredResourceDescriptor) { - return MONITORED_RESOURCE_DESCRIPTOR_PATH_TEMPLATE.instantiate( - "project", project, - "monitored_resource_descriptor", monitoredResourceDescriptor); - } - - /** Parses the project from the given fully-qualified path which represents a project resource. */ - public static final String parseProjectFromProjectName(String projectName) { - return PROJECT_PATH_TEMPLATE.parse(projectName).get("project"); - } - - /** - * Parses the project from the given fully-qualified path which represents a metric_descriptor - * resource. - */ - public static final String parseProjectFromMetricDescriptorName(String metricDescriptorName) { - return METRIC_DESCRIPTOR_PATH_TEMPLATE.parse(metricDescriptorName).get("project"); - } - - /** - * Parses the metric_descriptor from the given fully-qualified path which represents a - * metric_descriptor resource. - */ - public static final String parseMetricDescriptorFromMetricDescriptorName( - String metricDescriptorName) { - return METRIC_DESCRIPTOR_PATH_TEMPLATE.parse(metricDescriptorName).get("metric_descriptor"); - } - - /** - * Parses the project from the given fully-qualified path which represents a - * monitored_resource_descriptor resource. - */ - public static final String parseProjectFromMonitoredResourceDescriptorName( - String monitoredResourceDescriptorName) { - return MONITORED_RESOURCE_DESCRIPTOR_PATH_TEMPLATE - .parse(monitoredResourceDescriptorName) - .get("project"); - } - - /** - * Parses the monitored_resource_descriptor from the given fully-qualified path which represents a - * monitored_resource_descriptor resource. - */ - public static final String parseMonitoredResourceDescriptorFromMonitoredResourceDescriptorName( - String monitoredResourceDescriptorName) { - return MONITORED_RESOURCE_DESCRIPTOR_PATH_TEMPLATE - .parse(monitoredResourceDescriptorName) - .get("monitored_resource_descriptor"); - } - /** Constructs an instance of MetricServiceClient with default settings. */ public static final MetricServiceClient create() throws IOException { return create(MetricServiceSettings.defaultBuilder().build()); @@ -301,8 +223,8 @@ public final MetricServiceSettings getSettings() { * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    -   *   for (MonitoredResourceDescriptor element : metricServiceClient.listMonitoredResourceDescriptors(formattedName).iterateAllElements()) {
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
    +   *   for (MonitoredResourceDescriptor element : metricServiceClient.listMonitoredResourceDescriptors(name).iterateAllElements()) {
        *     // doThingsWith(element);
        *   }
        * }
    @@ -313,10 +235,9 @@ public final MetricServiceSettings getSettings() {
        * @throws com.google.api.gax.grpc.ApiException if the remote call fails
        */
       public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResourceDescriptors(
    -      String name) {
    -    PROJECT_PATH_TEMPLATE.validate(name, "listMonitoredResourceDescriptors");
    +      ProjectName name) {
         ListMonitoredResourceDescriptorsRequest request =
    -        ListMonitoredResourceDescriptorsRequest.newBuilder().setName(name).build();
    +        ListMonitoredResourceDescriptorsRequest.newBuilder().setNameWithProjectName(name).build();
         return listMonitoredResourceDescriptors(request);
       }
     
    @@ -329,9 +250,9 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMonitoredResourceDescriptorsRequest request = ListMonitoredResourceDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   for (MonitoredResourceDescriptor element : metricServiceClient.listMonitoredResourceDescriptors(request).iterateAllElements()) {
        *     // doThingsWith(element);
    @@ -356,9 +277,9 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMonitoredResourceDescriptorsRequest request = ListMonitoredResourceDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   ListenableFuture<ListMonitoredResourceDescriptorsPagedResponse> future = metricServiceClient.listMonitoredResourceDescriptorsPagedCallable().futureCall(request);
        *   // Do something
    @@ -383,9 +304,9 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMonitoredResourceDescriptorsRequest request = ListMonitoredResourceDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   while (true) {
        *     ListMonitoredResourceDescriptorsResponse response = metricServiceClient.listMonitoredResourceDescriptorsCallable().call(request);
    @@ -417,8 +338,8 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMonitoredResourceDescriptorName("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    -   *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(formattedName);
    +   *   MonitoredResourceDescriptorName name = MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    +   *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(name);
        * }
        * 
    * @@ -427,10 +348,13 @@ public final ListMonitoredResourceDescriptorsPagedResponse listMonitoredResource * `{resource_type}` is a predefined type, such as `cloudsql_database`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final MonitoredResourceDescriptor getMonitoredResourceDescriptor(String name) { - MONITORED_RESOURCE_DESCRIPTOR_PATH_TEMPLATE.validate(name, "getMonitoredResourceDescriptor"); + public final MonitoredResourceDescriptor getMonitoredResourceDescriptor( + MonitoredResourceDescriptorName name) { + GetMonitoredResourceDescriptorRequest request = - GetMonitoredResourceDescriptorRequest.newBuilder().setName(name).build(); + GetMonitoredResourceDescriptorRequest.newBuilder() + .setNameWithMonitoredResourceDescriptorName(name) + .build(); return getMonitoredResourceDescriptor(request); } @@ -443,9 +367,9 @@ public final MonitoredResourceDescriptor getMonitoredResourceDescriptor(String n * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMonitoredResourceDescriptorName("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    +   *   MonitoredResourceDescriptorName name = MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
        *   GetMonitoredResourceDescriptorRequest request = GetMonitoredResourceDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMonitoredResourceDescriptorName(name)
        *     .build();
        *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(request);
        * }
    @@ -468,9 +392,9 @@ private final MonitoredResourceDescriptor getMonitoredResourceDescriptor(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMonitoredResourceDescriptorName("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    +   *   MonitoredResourceDescriptorName name = MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
        *   GetMonitoredResourceDescriptorRequest request = GetMonitoredResourceDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMonitoredResourceDescriptorName(name)
        *     .build();
        *   ListenableFuture<MonitoredResourceDescriptor> future = metricServiceClient.getMonitoredResourceDescriptorCallable().futureCall(request);
        *   // Do something
    @@ -492,8 +416,8 @@ private final MonitoredResourceDescriptor getMonitoredResourceDescriptor(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    -   *   for (MetricDescriptor element : metricServiceClient.listMetricDescriptors(formattedName).iterateAllElements()) {
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
    +   *   for (MetricDescriptor element : metricServiceClient.listMetricDescriptors(name).iterateAllElements()) {
        *     // doThingsWith(element);
        *   }
        * }
    @@ -503,10 +427,9 @@ private final MonitoredResourceDescriptor getMonitoredResourceDescriptor(
        *     `"projects/{project_id_or_number}"`.
        * @throws com.google.api.gax.grpc.ApiException if the remote call fails
        */
    -  public final ListMetricDescriptorsPagedResponse listMetricDescriptors(String name) {
    -    PROJECT_PATH_TEMPLATE.validate(name, "listMetricDescriptors");
    +  public final ListMetricDescriptorsPagedResponse listMetricDescriptors(ProjectName name) {
         ListMetricDescriptorsRequest request =
    -        ListMetricDescriptorsRequest.newBuilder().setName(name).build();
    +        ListMetricDescriptorsRequest.newBuilder().setNameWithProjectName(name).build();
         return listMetricDescriptors(request);
       }
     
    @@ -519,9 +442,9 @@ public final ListMetricDescriptorsPagedResponse listMetricDescriptors(String nam
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMetricDescriptorsRequest request = ListMetricDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   for (MetricDescriptor element : metricServiceClient.listMetricDescriptors(request).iterateAllElements()) {
        *     // doThingsWith(element);
    @@ -546,9 +469,9 @@ public final ListMetricDescriptorsPagedResponse listMetricDescriptors(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMetricDescriptorsRequest request = ListMetricDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   ListenableFuture<ListMetricDescriptorsPagedResponse> future = metricServiceClient.listMetricDescriptorsPagedCallable().futureCall(request);
        *   // Do something
    @@ -572,9 +495,9 @@ public final ListMetricDescriptorsPagedResponse listMetricDescriptors(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   ListMetricDescriptorsRequest request = ListMetricDescriptorsRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .build();
        *   while (true) {
        *     ListMetricDescriptorsResponse response = metricServiceClient.listMetricDescriptorsCallable().call(request);
    @@ -604,8 +527,8 @@ public final ListMetricDescriptorsPagedResponse listMetricDescriptors(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    -   *   MetricDescriptor response = metricServiceClient.getMetricDescriptor(formattedName);
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   MetricDescriptor response = metricServiceClient.getMetricDescriptor(name);
        * }
        * 
    * @@ -614,10 +537,10 @@ public final ListMetricDescriptorsPagedResponse listMetricDescriptors( * `{metric_id}` is `"compute.googleapis.com/instance/disk/read_bytes_count"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final MetricDescriptor getMetricDescriptor(String name) { - METRIC_DESCRIPTOR_PATH_TEMPLATE.validate(name, "getMetricDescriptor"); + public final MetricDescriptor getMetricDescriptor(MetricDescriptorName name) { + GetMetricDescriptorRequest request = - GetMetricDescriptorRequest.newBuilder().setName(name).build(); + GetMetricDescriptorRequest.newBuilder().setNameWithMetricDescriptorName(name).build(); return getMetricDescriptor(request); } @@ -629,9 +552,9 @@ public final MetricDescriptor getMetricDescriptor(String name) { * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
        *   GetMetricDescriptorRequest request = GetMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMetricDescriptorName(name)
        *     .build();
        *   MetricDescriptor response = metricServiceClient.getMetricDescriptor(request);
        * }
    @@ -652,9 +575,9 @@ private final MetricDescriptor getMetricDescriptor(GetMetricDescriptorRequest re
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
        *   GetMetricDescriptorRequest request = GetMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMetricDescriptorName(name)
        *     .build();
        *   ListenableFuture<MetricDescriptor> future = metricServiceClient.getMetricDescriptorCallable().futureCall(request);
        *   // Do something
    @@ -676,9 +599,9 @@ private final MetricDescriptor getMetricDescriptor(GetMetricDescriptorRequest re
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   MetricDescriptor metricDescriptor = MetricDescriptor.newBuilder().build();
    -   *   MetricDescriptor response = metricServiceClient.createMetricDescriptor(formattedName, metricDescriptor);
    +   *   MetricDescriptor response = metricServiceClient.createMetricDescriptor(name, metricDescriptor);
        * }
        * 
    * @@ -688,11 +611,11 @@ private final MetricDescriptor getMetricDescriptor(GetMetricDescriptorRequest re * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ public final MetricDescriptor createMetricDescriptor( - String name, MetricDescriptor metricDescriptor) { - PROJECT_PATH_TEMPLATE.validate(name, "createMetricDescriptor"); + ProjectName name, MetricDescriptor metricDescriptor) { + CreateMetricDescriptorRequest request = CreateMetricDescriptorRequest.newBuilder() - .setName(name) + .setNameWithProjectName(name) .setMetricDescriptor(metricDescriptor) .build(); return createMetricDescriptor(request); @@ -707,10 +630,10 @@ public final MetricDescriptor createMetricDescriptor( * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   MetricDescriptor metricDescriptor = MetricDescriptor.newBuilder().build();
        *   CreateMetricDescriptorRequest request = CreateMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setMetricDescriptor(metricDescriptor)
        *     .build();
        *   MetricDescriptor response = metricServiceClient.createMetricDescriptor(request);
    @@ -733,10 +656,10 @@ public final MetricDescriptor createMetricDescriptor(CreateMetricDescriptorReque
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   MetricDescriptor metricDescriptor = MetricDescriptor.newBuilder().build();
        *   CreateMetricDescriptorRequest request = CreateMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setMetricDescriptor(metricDescriptor)
        *     .build();
        *   ListenableFuture<MetricDescriptor> future = metricServiceClient.createMetricDescriptorCallable().futureCall(request);
    @@ -759,8 +682,8 @@ public final MetricDescriptor createMetricDescriptor(CreateMetricDescriptorReque
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    -   *   metricServiceClient.deleteMetricDescriptor(formattedName);
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   metricServiceClient.deleteMetricDescriptor(name);
        * }
        * 
    * @@ -769,10 +692,10 @@ public final MetricDescriptor createMetricDescriptor(CreateMetricDescriptorReque * `{metric_id}` is: `"custom.googleapis.com/my_test_metric"`. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final void deleteMetricDescriptor(String name) { - METRIC_DESCRIPTOR_PATH_TEMPLATE.validate(name, "deleteMetricDescriptor"); + public final void deleteMetricDescriptor(MetricDescriptorName name) { + DeleteMetricDescriptorRequest request = - DeleteMetricDescriptorRequest.newBuilder().setName(name).build(); + DeleteMetricDescriptorRequest.newBuilder().setNameWithMetricDescriptorName(name).build(); deleteMetricDescriptor(request); } @@ -785,9 +708,9 @@ public final void deleteMetricDescriptor(String name) { * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
        *   DeleteMetricDescriptorRequest request = DeleteMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMetricDescriptorName(name)
        *     .build();
        *   metricServiceClient.deleteMetricDescriptor(request);
        * }
    @@ -809,9 +732,9 @@ private final void deleteMetricDescriptor(DeleteMetricDescriptorRequest request)
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]");
    +   *   MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]");
        *   DeleteMetricDescriptorRequest request = DeleteMetricDescriptorRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithMetricDescriptorName(name)
        *     .build();
        *   ListenableFuture<Void> future = metricServiceClient.deleteMetricDescriptorCallable().futureCall(request);
        *   // Do something
    @@ -832,11 +755,11 @@ private final void deleteMetricDescriptor(DeleteMetricDescriptorRequest request)
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   String filter = "";
        *   TimeInterval interval = TimeInterval.newBuilder().build();
        *   ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL;
    -   *   for (TimeSeries element : metricServiceClient.listTimeSeries(formattedName, filter, interval, view).iterateAllElements()) {
    +   *   for (TimeSeries element : metricServiceClient.listTimeSeries(name, filter, interval, view).iterateAllElements()) {
        *     // doThingsWith(element);
        *   }
        * }
    @@ -855,14 +778,13 @@ private final void deleteMetricDescriptor(DeleteMetricDescriptorRequest request)
        * @throws com.google.api.gax.grpc.ApiException if the remote call fails
        */
       public final ListTimeSeriesPagedResponse listTimeSeries(
    -      String name,
    +      ProjectName name,
           String filter,
           TimeInterval interval,
           ListTimeSeriesRequest.TimeSeriesView view) {
    -    PROJECT_PATH_TEMPLATE.validate(name, "listTimeSeries");
         ListTimeSeriesRequest request =
             ListTimeSeriesRequest.newBuilder()
    -            .setName(name)
    +            .setNameWithProjectName(name)
                 .setFilter(filter)
                 .setInterval(interval)
                 .setView(view)
    @@ -878,12 +800,12 @@ public final ListTimeSeriesPagedResponse listTimeSeries(
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   String filter = "";
        *   TimeInterval interval = TimeInterval.newBuilder().build();
        *   ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL;
        *   ListTimeSeriesRequest request = ListTimeSeriesRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setFilter(filter)
        *     .setInterval(interval)
        *     .setView(view)
    @@ -909,12 +831,12 @@ public final ListTimeSeriesPagedResponse listTimeSeries(ListTimeSeriesRequest re
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   String filter = "";
        *   TimeInterval interval = TimeInterval.newBuilder().build();
        *   ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL;
        *   ListTimeSeriesRequest request = ListTimeSeriesRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setFilter(filter)
        *     .setInterval(interval)
        *     .setView(view)
    @@ -940,12 +862,12 @@ public final ListTimeSeriesPagedResponse listTimeSeries(ListTimeSeriesRequest re
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   String filter = "";
        *   TimeInterval interval = TimeInterval.newBuilder().build();
        *   ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL;
        *   ListTimeSeriesRequest request = ListTimeSeriesRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .setFilter(filter)
        *     .setInterval(interval)
        *     .setView(view)
    @@ -980,9 +902,9 @@ public final ListTimeSeriesPagedResponse listTimeSeries(ListTimeSeriesRequest re
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   List<TimeSeries> timeSeries = new ArrayList<>();
    -   *   metricServiceClient.createTimeSeries(formattedName, timeSeries);
    +   *   metricServiceClient.createTimeSeries(name, timeSeries);
        * }
        * 
    * @@ -994,10 +916,13 @@ public final ListTimeSeriesPagedResponse listTimeSeries(ListTimeSeriesRequest re * by supplying all label values for the metric and the monitored resource. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - public final void createTimeSeries(String name, List timeSeries) { - PROJECT_PATH_TEMPLATE.validate(name, "createTimeSeries"); + public final void createTimeSeries(ProjectName name, List timeSeries) { + CreateTimeSeriesRequest request = - CreateTimeSeriesRequest.newBuilder().setName(name).addAllTimeSeries(timeSeries).build(); + CreateTimeSeriesRequest.newBuilder() + .setNameWithProjectName(name) + .addAllTimeSeries(timeSeries) + .build(); createTimeSeries(request); } @@ -1011,10 +936,10 @@ public final void createTimeSeries(String name, List timeSeries) { * *
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   List<TimeSeries> timeSeries = new ArrayList<>();
        *   CreateTimeSeriesRequest request = CreateTimeSeriesRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .addAllTimeSeries(timeSeries)
        *     .build();
        *   metricServiceClient.createTimeSeries(request);
    @@ -1038,10 +963,10 @@ public final void createTimeSeries(CreateTimeSeriesRequest request) {
        *
        * 
    
        * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    -   *   String formattedName = MetricServiceClient.formatProjectName("[PROJECT]");
    +   *   ProjectName name = ProjectName.create("[PROJECT]");
        *   List<TimeSeries> timeSeries = new ArrayList<>();
        *   CreateTimeSeriesRequest request = CreateTimeSeriesRequest.newBuilder()
    -   *     .setName(formattedName)
    +   *     .setNameWithProjectName(name)
        *     .addAllTimeSeries(timeSeries)
        *     .build();
        *   ListenableFuture<Void> future = metricServiceClient.createTimeSeriesCallable().futureCall(request);
    diff --git a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/package-info.java b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/package-info.java
    index ee65c23d2533..95db6ed368b8 100644
    --- a/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/package-info.java
    +++ b/google-cloud-monitoring/src/main/java/com/google/cloud/monitoring/spi/v3/package-info.java
    @@ -36,8 +36,8 @@
      * 
      * 
      * try (GroupServiceClient groupServiceClient = GroupServiceClient.create()) {
    - *   String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]");
    - *   Group response = groupServiceClient.getGroup(formattedName);
    + *   GroupName name = GroupName.create("[PROJECT]", "[GROUP]");
    + *   Group response = groupServiceClient.getGroup(name);
      * }
      * 
      * 
    @@ -52,8 +52,8 @@ *
      * 
      * try (MetricServiceClient metricServiceClient = MetricServiceClient.create()) {
    - *   String formattedName = MetricServiceClient.formatMonitoredResourceDescriptorName("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    - *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(formattedName);
    + *   MonitoredResourceDescriptorName name = MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]");
    + *   MonitoredResourceDescriptor response = metricServiceClient.getMonitoredResourceDescriptor(name);
      * }
      * 
      * 
    diff --git a/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/GroupServiceTest.java b/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/GroupServiceTest.java index 638dce922115..b4194bfa0fb1 100644 --- a/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/GroupServiceTest.java +++ b/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/GroupServiceTest.java @@ -26,8 +26,10 @@ import com.google.monitoring.v3.DeleteGroupRequest; import com.google.monitoring.v3.GetGroupRequest; import com.google.monitoring.v3.Group; +import com.google.monitoring.v3.GroupName; import com.google.monitoring.v3.ListGroupMembersRequest; import com.google.monitoring.v3.ListGroupMembersResponse; +import com.google.monitoring.v3.ProjectName; import com.google.monitoring.v3.UpdateGroupRequest; import com.google.protobuf.Empty; import com.google.protobuf.GeneratedMessageV3; @@ -83,31 +85,31 @@ public void tearDown() throws Exception { @Test @SuppressWarnings("all") public void getGroupTest() { - String formattedName2 = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name2 = GroupName.create("[PROJECT]", "[GROUP]"); String displayName = "displayName1615086568"; - String parentName = "parentName1015022848"; + GroupName parentName = GroupName.create("[PROJECT]", "[GROUP]"); String filter = "filter-1274492040"; boolean isCluster = false; Group expectedResponse = Group.newBuilder() - .setName(formattedName2) + .setNameWithGroupName(name2) .setDisplayName(displayName) - .setParentName(parentName) + .setParentNameWithGroupName(parentName) .setFilter(filter) .setIsCluster(isCluster) .build(); mockGroupService.addResponse(expectedResponse); - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - Group actualResponse = client.getGroup(formattedName); + Group actualResponse = client.getGroup(name); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockGroupService.getRequests(); Assert.assertEquals(1, actualRequests.size()); GetGroupRequest actualRequest = (GetGroupRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsGroupName()); } @Test @@ -117,9 +119,9 @@ public void getGroupExceptionTest() throws Exception { mockGroupService.addException(exception); try { - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - client.getGroup(formattedName); + client.getGroup(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -129,32 +131,32 @@ public void getGroupExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void createGroupTest() { - String formattedName2 = GroupServiceClient.formatProjectName("[PROJECT]"); + GroupName name2 = GroupName.create("[PROJECT]", "[GROUP]"); String displayName = "displayName1615086568"; - String parentName = "parentName1015022848"; + GroupName parentName = GroupName.create("[PROJECT]", "[GROUP]"); String filter = "filter-1274492040"; boolean isCluster = false; Group expectedResponse = Group.newBuilder() - .setName(formattedName2) + .setNameWithGroupName(name2) .setDisplayName(displayName) - .setParentName(parentName) + .setParentNameWithGroupName(parentName) .setFilter(filter) .setIsCluster(isCluster) .build(); mockGroupService.addResponse(expectedResponse); - String formattedName = GroupServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); Group group = Group.newBuilder().build(); - Group actualResponse = client.createGroup(formattedName, group); + Group actualResponse = client.createGroup(name, group); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockGroupService.getRequests(); Assert.assertEquals(1, actualRequests.size()); CreateGroupRequest actualRequest = (CreateGroupRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); Assert.assertEquals(group, actualRequest.getGroup()); } @@ -165,10 +167,10 @@ public void createGroupExceptionTest() throws Exception { mockGroupService.addException(exception); try { - String formattedName = GroupServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); Group group = Group.newBuilder().build(); - client.createGroup(formattedName, group); + client.createGroup(name, group); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -178,16 +180,16 @@ public void createGroupExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void updateGroupTest() { - String name = "name3373707"; + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); String displayName = "displayName1615086568"; - String parentName = "parentName1015022848"; + GroupName parentName = GroupName.create("[PROJECT]", "[GROUP]"); String filter = "filter-1274492040"; boolean isCluster = false; Group expectedResponse = Group.newBuilder() - .setName(name) + .setNameWithGroupName(name) .setDisplayName(displayName) - .setParentName(parentName) + .setParentNameWithGroupName(parentName) .setFilter(filter) .setIsCluster(isCluster) .build(); @@ -227,15 +229,15 @@ public void deleteGroupTest() { Empty expectedResponse = Empty.newBuilder().build(); mockGroupService.addResponse(expectedResponse); - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - client.deleteGroup(formattedName); + client.deleteGroup(name); List actualRequests = mockGroupService.getRequests(); Assert.assertEquals(1, actualRequests.size()); DeleteGroupRequest actualRequest = (DeleteGroupRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsGroupName()); } @Test @@ -245,9 +247,9 @@ public void deleteGroupExceptionTest() throws Exception { mockGroupService.addException(exception); try { - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - client.deleteGroup(formattedName); + client.deleteGroup(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -269,9 +271,9 @@ public void listGroupMembersTest() { .build(); mockGroupService.addResponse(expectedResponse); - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - ListGroupMembersPagedResponse pagedListResponse = client.listGroupMembers(formattedName); + ListGroupMembersPagedResponse pagedListResponse = client.listGroupMembers(name); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); Assert.assertEquals(1, resources.size()); @@ -281,7 +283,7 @@ public void listGroupMembersTest() { Assert.assertEquals(1, actualRequests.size()); ListGroupMembersRequest actualRequest = (ListGroupMembersRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsGroupName()); } @Test @@ -291,9 +293,9 @@ public void listGroupMembersExceptionTest() throws Exception { mockGroupService.addException(exception); try { - String formattedName = GroupServiceClient.formatGroupName("[PROJECT]", "[GROUP]"); + GroupName name = GroupName.create("[PROJECT]", "[GROUP]"); - client.listGroupMembers(formattedName); + client.listGroupMembers(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); diff --git a/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/MetricServiceTest.java b/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/MetricServiceTest.java index 49a93045749b..ec9fcee28fff 100644 --- a/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/MetricServiceTest.java +++ b/google-cloud-monitoring/src/test/java/com/google/cloud/monitoring/spi/v3/MetricServiceTest.java @@ -37,6 +37,9 @@ import com.google.monitoring.v3.ListTimeSeriesRequest; import com.google.monitoring.v3.ListTimeSeriesRequest.TimeSeriesView; import com.google.monitoring.v3.ListTimeSeriesResponse; +import com.google.monitoring.v3.MetricDescriptorName; +import com.google.monitoring.v3.MonitoredResourceDescriptorName; +import com.google.monitoring.v3.ProjectName; import com.google.monitoring.v3.TimeInterval; import com.google.monitoring.v3.TimeSeries; import com.google.protobuf.Empty; @@ -106,10 +109,10 @@ public void listMonitoredResourceDescriptorsTest() { .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); ListMonitoredResourceDescriptorsPagedResponse pagedListResponse = - client.listMonitoredResourceDescriptors(formattedName); + client.listMonitoredResourceDescriptors(name); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); @@ -121,7 +124,7 @@ public void listMonitoredResourceDescriptorsTest() { ListMonitoredResourceDescriptorsRequest actualRequest = (ListMonitoredResourceDescriptorsRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); } @Test @@ -131,9 +134,9 @@ public void listMonitoredResourceDescriptorsExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); - client.listMonitoredResourceDescriptors(formattedName); + client.listMonitoredResourceDescriptors(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -143,27 +146,23 @@ public void listMonitoredResourceDescriptorsExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void getMonitoredResourceDescriptorTest() { - String formattedName2 = - MetricServiceClient.formatMonitoredResourceDescriptorName( - "[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]"); + String name2 = "name2-1052831874"; String type = "type3575610"; String displayName = "displayName1615086568"; String description = "description-1724546052"; MonitoredResourceDescriptor expectedResponse = MonitoredResourceDescriptor.newBuilder() - .setName(formattedName2) + .setName(name2) .setType(type) .setDisplayName(displayName) .setDescription(description) .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = - MetricServiceClient.formatMonitoredResourceDescriptorName( - "[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]"); + MonitoredResourceDescriptorName name = + MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]"); - MonitoredResourceDescriptor actualResponse = - client.getMonitoredResourceDescriptor(formattedName); + MonitoredResourceDescriptor actualResponse = client.getMonitoredResourceDescriptor(name); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockMetricService.getRequests(); @@ -171,7 +170,7 @@ public void getMonitoredResourceDescriptorTest() { GetMonitoredResourceDescriptorRequest actualRequest = (GetMonitoredResourceDescriptorRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsMonitoredResourceDescriptorName()); } @Test @@ -181,11 +180,10 @@ public void getMonitoredResourceDescriptorExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = - MetricServiceClient.formatMonitoredResourceDescriptorName( - "[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]"); + MonitoredResourceDescriptorName name = + MonitoredResourceDescriptorName.create("[PROJECT]", "[MONITORED_RESOURCE_DESCRIPTOR]"); - client.getMonitoredResourceDescriptor(formattedName); + client.getMonitoredResourceDescriptor(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -205,10 +203,9 @@ public void listMetricDescriptorsTest() { .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); - ListMetricDescriptorsPagedResponse pagedListResponse = - client.listMetricDescriptors(formattedName); + ListMetricDescriptorsPagedResponse pagedListResponse = client.listMetricDescriptors(name); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); Assert.assertEquals(1, resources.size()); @@ -219,7 +216,7 @@ public void listMetricDescriptorsTest() { ListMetricDescriptorsRequest actualRequest = (ListMetricDescriptorsRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); } @Test @@ -229,9 +226,9 @@ public void listMetricDescriptorsExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); - client.listMetricDescriptors(formattedName); + client.listMetricDescriptors(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -241,15 +238,14 @@ public void listMetricDescriptorsExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void getMetricDescriptorTest() { - String formattedName2 = - MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]"); + String name2 = "name2-1052831874"; String type = "type3575610"; String unit = "unit3594628"; String description = "description-1724546052"; String displayName = "displayName1615086568"; MetricDescriptor expectedResponse = MetricDescriptor.newBuilder() - .setName(formattedName2) + .setName(name2) .setType(type) .setUnit(unit) .setDescription(description) @@ -257,17 +253,16 @@ public void getMetricDescriptorTest() { .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = - MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]"); + MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]"); - MetricDescriptor actualResponse = client.getMetricDescriptor(formattedName); + MetricDescriptor actualResponse = client.getMetricDescriptor(name); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockMetricService.getRequests(); Assert.assertEquals(1, actualRequests.size()); GetMetricDescriptorRequest actualRequest = (GetMetricDescriptorRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsMetricDescriptorName()); } @Test @@ -277,10 +272,9 @@ public void getMetricDescriptorExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = - MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]"); + MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]"); - client.getMetricDescriptor(formattedName); + client.getMetricDescriptor(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -290,14 +284,14 @@ public void getMetricDescriptorExceptionTest() throws Exception { @Test @SuppressWarnings("all") public void createMetricDescriptorTest() { - String formattedName2 = MetricServiceClient.formatProjectName("[PROJECT]"); + String name2 = "name2-1052831874"; String type = "type3575610"; String unit = "unit3594628"; String description = "description-1724546052"; String displayName = "displayName1615086568"; MetricDescriptor expectedResponse = MetricDescriptor.newBuilder() - .setName(formattedName2) + .setName(name2) .setType(type) .setUnit(unit) .setDescription(description) @@ -305,11 +299,10 @@ public void createMetricDescriptorTest() { .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); MetricDescriptor metricDescriptor = MetricDescriptor.newBuilder().build(); - MetricDescriptor actualResponse = - client.createMetricDescriptor(formattedName, metricDescriptor); + MetricDescriptor actualResponse = client.createMetricDescriptor(name, metricDescriptor); Assert.assertEquals(expectedResponse, actualResponse); List actualRequests = mockMetricService.getRequests(); @@ -317,7 +310,7 @@ public void createMetricDescriptorTest() { CreateMetricDescriptorRequest actualRequest = (CreateMetricDescriptorRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); Assert.assertEquals(metricDescriptor, actualRequest.getMetricDescriptor()); } @@ -328,10 +321,10 @@ public void createMetricDescriptorExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); MetricDescriptor metricDescriptor = MetricDescriptor.newBuilder().build(); - client.createMetricDescriptor(formattedName, metricDescriptor); + client.createMetricDescriptor(name, metricDescriptor); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -344,17 +337,16 @@ public void deleteMetricDescriptorTest() { Empty expectedResponse = Empty.newBuilder().build(); mockMetricService.addResponse(expectedResponse); - String formattedName = - MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]"); + MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]"); - client.deleteMetricDescriptor(formattedName); + client.deleteMetricDescriptor(name); List actualRequests = mockMetricService.getRequests(); Assert.assertEquals(1, actualRequests.size()); DeleteMetricDescriptorRequest actualRequest = (DeleteMetricDescriptorRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsMetricDescriptorName()); } @Test @@ -364,10 +356,9 @@ public void deleteMetricDescriptorExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = - MetricServiceClient.formatMetricDescriptorName("[PROJECT]", "[METRIC_DESCRIPTOR]"); + MetricDescriptorName name = MetricDescriptorName.create("[PROJECT]", "[METRIC_DESCRIPTOR]"); - client.deleteMetricDescriptor(formattedName); + client.deleteMetricDescriptor(name); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -387,13 +378,13 @@ public void listTimeSeriesTest() { .build(); mockMetricService.addResponse(expectedResponse); - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); String filter = "filter-1274492040"; TimeInterval interval = TimeInterval.newBuilder().build(); ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL; ListTimeSeriesPagedResponse pagedListResponse = - client.listTimeSeries(formattedName, filter, interval, view); + client.listTimeSeries(name, filter, interval, view); List resources = Lists.newArrayList(pagedListResponse.iterateAllElements()); Assert.assertEquals(1, resources.size()); @@ -403,7 +394,7 @@ public void listTimeSeriesTest() { Assert.assertEquals(1, actualRequests.size()); ListTimeSeriesRequest actualRequest = (ListTimeSeriesRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); Assert.assertEquals(filter, actualRequest.getFilter()); Assert.assertEquals(interval, actualRequest.getInterval()); Assert.assertEquals(view, actualRequest.getView()); @@ -416,12 +407,12 @@ public void listTimeSeriesExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); String filter = "filter-1274492040"; TimeInterval interval = TimeInterval.newBuilder().build(); ListTimeSeriesRequest.TimeSeriesView view = ListTimeSeriesRequest.TimeSeriesView.FULL; - client.listTimeSeries(formattedName, filter, interval, view); + client.listTimeSeries(name, filter, interval, view); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); @@ -434,16 +425,16 @@ public void createTimeSeriesTest() { Empty expectedResponse = Empty.newBuilder().build(); mockMetricService.addResponse(expectedResponse); - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); List timeSeries = new ArrayList<>(); - client.createTimeSeries(formattedName, timeSeries); + client.createTimeSeries(name, timeSeries); List actualRequests = mockMetricService.getRequests(); Assert.assertEquals(1, actualRequests.size()); CreateTimeSeriesRequest actualRequest = (CreateTimeSeriesRequest) actualRequests.get(0); - Assert.assertEquals(formattedName, actualRequest.getName()); + Assert.assertEquals(name, actualRequest.getNameAsProjectName()); Assert.assertEquals(timeSeries, actualRequest.getTimeSeriesList()); } @@ -454,10 +445,10 @@ public void createTimeSeriesExceptionTest() throws Exception { mockMetricService.addException(exception); try { - String formattedName = MetricServiceClient.formatProjectName("[PROJECT]"); + ProjectName name = ProjectName.create("[PROJECT]"); List timeSeries = new ArrayList<>(); - client.createTimeSeries(formattedName, timeSeries); + client.createTimeSeries(name, timeSeries); Assert.fail("No exception raised"); } catch (ApiException e) { Assert.assertEquals(Status.INTERNAL.getCode(), e.getStatusCode()); diff --git a/google-cloud-pubsub/README.md b/google-cloud-pubsub/README.md index dc60848f7bed..23a299fffdd5 100644 --- a/google-cloud-pubsub/README.md +++ b/google-cloud-pubsub/README.md @@ -26,16 +26,16 @@ Add this to your pom.xml file com.google.cloud google-cloud-pubsub - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-pubsub:0.8.0' +compile 'com.google.cloud:google-cloud-pubsub:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-pubsub" % "0.8.2-alpha" ``` Example Application diff --git a/google-cloud-pubsub/pom.xml b/google-cloud-pubsub/pom.xml index da1acb3fc62d..c6ae30e4f1bb 100644 --- a/google-cloud-pubsub/pom.xml +++ b/google-cloud-pubsub/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-pubsub diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/AckDeadlineRenewer.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/AckDeadlineRenewer.java new file mode 100644 index 000000000000..8bc81606c1ab --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/AckDeadlineRenewer.java @@ -0,0 +1,317 @@ +/* + * 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.pubsub; + +import com.google.cloud.Clock; +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.common.base.MoreObjects; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Class for an automatic ack deadline renewer. An ack deadline renewer automatically renews the + * acknowledge deadline of messages added to it (via {@link #add(String, String)} or + * {@link #add(String, Iterable)}. The acknowledge deadlines of added messages are renewed until the + * messages are explicitly removed using {@link #remove(String, String)}. + */ +class AckDeadlineRenewer implements AutoCloseable { + + private static final int MIN_DEADLINE_MILLIS = 10_000; + private static final int DEADLINE_SLACK_MILLIS = 1_000; + private static final int RENEW_THRESHOLD_MILLIS = 3_000; + private static final int NEXT_RENEWAL_THRESHOLD_MILLIS = 1_000; + + private final PubSub pubsub; + private final ScheduledExecutorService executor; + private final ExecutorFactory executorFactory; + private final Clock clock; + private final Queue messageQueue; + private final Map messageDeadlines; + private final Object lock = new Object(); + private final Object futureLock = new Object(); + private Future renewerFuture; + private boolean closed; + + /** + * This class holds the identity of a message to renew: subscription and acknowledge id. + */ + private static class MessageId { + + private final String subscription; + private final String ackId; + + MessageId(String subscription, String ackId) { + this.subscription = subscription; + this.ackId = ackId; + } + + String subscription() { + return subscription; + } + + String ackId() { + return ackId; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof MessageId)) { + return false; + } + MessageId other = (MessageId) obj; + return Objects.equals(other.subscription, this.subscription) + && Objects.equals(other.ackId, this.ackId); + } + + @Override + public int hashCode() { + return Objects.hash(subscription, ackId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("subscription", subscription) + .add("ackId", ackId) + .toString(); + } + } + + /** + * This class holds the identity of a message to renew and its expected ack deadline. + */ + private static final class Message { + + private final MessageId messageId; + private final Long deadline; + + Message(MessageId messageId, Long deadline) { + this.messageId = messageId; + this.deadline = deadline; + } + + MessageId messageId() { + return messageId; + } + + Long expectedDeadline() { + return deadline; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Message)) { + return false; + } + Message other = (Message) obj; + return Objects.equals(other.messageId, this.messageId) + && Objects.equals(other.deadline, this.deadline); + } + + @Override + public int hashCode() { + return Objects.hash(messageId, deadline); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("messageId", messageId) + .add("expectedDeadline", deadline) + .toString(); + } + } + + AckDeadlineRenewer(PubSub pubsub) { + PubSubOptions options = pubsub.getOptions(); + this.pubsub = pubsub; + this.executorFactory = options.getExecutorFactory(); + this.executor = executorFactory.get(); + this.clock = options.getClock(); + this.messageQueue = new LinkedList<>(); + this.messageDeadlines = new HashMap<>(); + } + + private void unsetAndScheduleNextRenewal() { + synchronized (futureLock) { + renewerFuture = null; + scheduleNextRenewal(); + } + } + + private void scheduleNextRenewal() { + // Schedules next renewal if there are still messages to process and no renewals scheduled that + // could handle them, otherwise does nothing + Message nextMessage; + synchronized (lock) { + Message peek = messageQueue.peek(); + // We remove from the queue messages that were removed from the ack deadline renewer (and + // possibly re-added) + while (peek != null && (!messageDeadlines.containsKey(peek.messageId()) + || messageDeadlines.get(peek.messageId()) > peek.expectedDeadline())) { + messageQueue.poll(); + peek = messageQueue.peek(); + } + nextMessage = peek; + } + synchronized (futureLock) { + if (renewerFuture == null && nextMessage != null) { + long delay = + (nextMessage.expectedDeadline() - clock.millis()) - NEXT_RENEWAL_THRESHOLD_MILLIS; + renewerFuture = executor.schedule(new Runnable() { + @Override + public void run() { + renewAckDeadlines(); + } + }, delay, TimeUnit.MILLISECONDS); + } + } + } + + private void renewAckDeadlines() { + ListMultimap messagesToRenewNext = LinkedListMultimap.create(); + // At every activation we renew all ack deadlines that will expire in the following + // RENEW_THRESHOLD_MILLIS + long threshold = clock.millis() + RENEW_THRESHOLD_MILLIS; + Message message; + while ((message = nextMessageToRenew(threshold)) != null) { + // If the expected deadline is null the message was removed and we must ignore it, otherwise + // we renew its ack deadline + if (message.expectedDeadline() != null) { + messagesToRenewNext.put(message.messageId().subscription(), message.messageId().ackId()); + } + } + for (Map.Entry> entry : Multimaps.asMap(messagesToRenewNext).entrySet()) { + // We send all ack deadline renewals for a subscription + pubsub.modifyAckDeadlineAsync(entry.getKey(), MIN_DEADLINE_MILLIS, TimeUnit.MILLISECONDS, + entry.getValue()); + } + unsetAndScheduleNextRenewal(); + } + + private Message nextMessageToRenew(long threshold) { + synchronized (lock) { + Message message = messageQueue.peek(); + // if the queue is empty or the next expected deadline is after threshold we stop + if (message == null || message.expectedDeadline() > threshold) { + return null; + } + MessageId messageId = messageQueue.poll().messageId(); + // Check if the next expected deadline changed. This can happen if the message was removed + // from the ack deadline renewer or if it was nacked and then pulled again + Long deadline = messageDeadlines.get(messageId); + if (deadline == null || deadline > threshold) { + // the message was removed (deadline == null) or removed and then added back + // (deadline > threshold), we should not renew its deadline (yet) + return new Message(messageId, null); + } else { + // Message deadline must be renewed, we must submit it again to the renewer + add(messageId.subscription(), messageId.ackId()); + return new Message(messageId, deadline); + } + } + } + + /** + * Adds a new message for which the acknowledge deadline should be automatically renewed. The + * message is identified by the subscription from which it was pulled and its acknowledge id. + * Auto-renewal will take place until the message is removed (see + * {@link #remove(String, String)}). + * + * @param subscription the subscription from which the message has been pulled + * @param ackId the message's acknowledge id + */ + void add(String subscription, String ackId) { + synchronized (lock) { + long deadline = clock.millis() + MIN_DEADLINE_MILLIS - DEADLINE_SLACK_MILLIS; + Message message = new Message(new MessageId(subscription, ackId), deadline); + messageQueue.add(message); + messageDeadlines.put(message.messageId(), deadline); + } + scheduleNextRenewal(); + } + + /** + * Adds new messages for which the acknowledge deadlined should be automatically renewed. The + * messages are identified by the subscription from which they were pulled and their + * acknowledge id. Auto-renewal will take place until the messages are removed (see + * {@link #remove(String, String)}). + * + * @param subscription the subscription from which the messages have been pulled + * @param ackIds the acknowledge ids of the messages + */ + void add(String subscription, Iterable ackIds) { + synchronized (lock) { + long deadline = clock.millis() + MIN_DEADLINE_MILLIS - DEADLINE_SLACK_MILLIS; + for (String ackId : ackIds) { + Message message = new Message(new MessageId(subscription, ackId), deadline); + messageQueue.add(message); + messageDeadlines.put(message.messageId(), deadline); + } + } + scheduleNextRenewal(); + } + + /** + * Removes a message from this {@code AckDeadlineRenewer}. The message is identified by the + * subscription from which it was pulled and its acknowledge id. Once the message is removed from + * this {@code AckDeadlineRenewer}, automated ack deadline renewals will stop. + * + * @param subscription the subscription from which the message has been pulled + * @param ackId the message's acknowledge id + */ + void remove(String subscription, String ackId) { + synchronized (lock) { + messageDeadlines.remove(new MessageId(subscription, ackId)); + } + } + + @Override + public void close() throws Exception { + if (closed) { + return; + } + closed = true; + synchronized (lock) { + messageDeadlines.clear(); + messageQueue.clear(); + } + synchronized (futureLock) { + if (renewerFuture != null) { + renewerFuture.cancel(true); + } + } + executorFactory.release(executor); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Message.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Message.java new file mode 100644 index 000000000000..d728dad43aea --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Message.java @@ -0,0 +1,454 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.cloud.ByteArray; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import com.google.pubsub.v1.PubsubMessage; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * A Google Cloud Pub/Sub message. A message is the combination of data and (optional) attributes + * that a publisher sends to a topic and is eventually delivered to subscribers. + * + *

    Message attributes are key-value pairs that a publisher can define for a message. For example, + * a key {@code iana.org/language_tag} and value {@code en} could be added to messages to mark them + * as readable by an English-speaking subscriber. + * + *

    To be published, a message must have a non-empty payload, or at least one attribute. + * + * @see Pub/Sub Data Model + */ +public class Message implements Serializable { + + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public PubsubMessage apply(Message message) { + return message.toPb(); + } + }; + + private static final long serialVersionUID = -1436515787233340634L; + private static final long NANOS_PER_MILLISECOND = 1000000; + private static final long MILLIS_PER_SECOND = 1000; + + private final String id; + private final InternalByteArray payload; + private final ImmutableMap attributes; + private final Long publishTime; + + private static final class InternalByteArray extends ByteArray { + + private static final long serialVersionUID = -3330181485911805428L; + + InternalByteArray(ByteString byteString) { + super(byteString); + } + + InternalByteArray(ByteArray byteArray) { + super(byteArray); + } + + @Override + protected ByteString getByteString() { + return super.getByteString(); + } + } + + /** + * Builder for {@code Message} objects. + */ + public abstract static class Builder { + + abstract Builder setId(String id); + + /** + * Sets the message payload to the provided string. The string is enconded {@code UTF-8}. + */ + @Deprecated + public abstract Builder payload(String payload); + + /** + * Sets the message payload to the provided string. The string is enconded {@code UTF-8}. + */ + public abstract Builder setPayload(String payload); + + /** + * Sets the message payload to the provided {@link ByteArray}. + */ + @Deprecated + public abstract Builder payload(ByteArray payload); + + /** + * Sets the message payload to the provided {@link ByteArray}. + */ + public abstract Builder setPayload(ByteArray payload); + + /** + * Sets the message attributes to the provided map. Message attributes are key-value pairs that + * a publisher can define for a message. For example, a key {@code iana.org/language_tag} and + * value {@code en} could be added to messages to mark them as readable by an English-speaking + * subscriber. + */ + @Deprecated + public abstract Builder attributes(Map attributes); + + /** + * Sets the message attributes to the provided map. Message attributes are key-value pairs that + * a publisher can define for a message. For example, a key {@code iana.org/language_tag} and + * value {@code en} could be added to messages to mark them as readable by an English-speaking + * subscriber. + */ + public abstract Builder setAttributes(Map attributes); + + /** + * Adds a new attribute to the message attributes. If an attribute with name {@code name} was + * already set, its value is updated. + */ + public abstract Builder addAttribute(String name, String value); + + /** + * Removes an attribute give its name from the message attributes. + */ + public abstract Builder removeAttribute(String name); + + /** + * Clears all message attributes. + */ + public abstract Builder clearAttributes(); + + abstract Builder setPublishTime(long publishTime); + + /** + * Creates a message object. + */ + public abstract Message build(); + } + + static final class BuilderImpl extends Builder { + + private String id; + private ByteArray payload; + private Map attributes = new HashMap<>(); + private Long publishTime; + + private BuilderImpl() {} + + BuilderImpl(Message message) { + id = message.id; + payload = message.payload; + attributes = new HashMap<>(message.attributes); + publishTime = message.publishTime; + } + + @Override + BuilderImpl setId(String id) { + this.id = checkNotNull(id); + return this; + } + + @Override + @Deprecated + public Builder payload(String payload) { + return setPayload(payload); + } + + @Override + public Builder setPayload(String payload) { + return setPayload(ByteArray.copyFrom(payload)); + } + + @Override + @Deprecated + public Builder payload(ByteArray payload) { + return setPayload(payload); + } + + @Override + public Builder setPayload(ByteArray payload) { + this.payload = payload; + return this; + } + + @Override + public Builder addAttribute(String name, String value) { + attributes.put(name, value); + return this; + } + + @Override + @Deprecated + public Builder attributes(Map attributes) { + return setAttributes(attributes); + } + + @Override + public Builder setAttributes(Map attributes) { + this.attributes = new HashMap<>(attributes); + return this; + } + + @Override + public Builder removeAttribute(String name) { + attributes.remove(name); + return this; + } + + @Override + public Builder clearAttributes() { + attributes.clear(); + return this; + } + + @Override + Builder setPublishTime(long publishTime) { + this.publishTime = publishTime; + return this; + } + + @Override + public Message build() { + return new Message(this); + } + } + + Message(BuilderImpl builder) { + id = builder.id; + payload = new InternalByteArray(checkNotNull(builder.payload)); + attributes = ImmutableMap.copyOf(builder.attributes); + publishTime = builder.publishTime; + } + + /** + * Returns the time in milliseconds at which the message was published. This value is set by the + * server when it receives the publish call. If not set, this method returns {@code null}. + */ + @Deprecated + public Long publishTime() { + return getPublishTime(); + } + + /** + * Returns the time in milliseconds at which the message was published. This value is set by the + * server when it receives the publish call. If not set, this method returns {@code null}. + */ + public Long getPublishTime() { + return publishTime; + } + + /** + * Returns the message attributes. Message attributes are key-value pairs that a publisher can + * define for a message. For example, a key {@code iana.org/language_tag} and value {@code en} + * could be added to messages to mark them as readable by an English-speaking subscriber. + */ + @Deprecated + public Map attributes() { + return getAttributes(); + } + + /** + * Returns the message attributes. Message attributes are key-value pairs that a publisher can + * define for a message. For example, a key {@code iana.org/language_tag} and value {@code en} + * could be added to messages to mark them as readable by an English-speaking subscriber. + */ + public Map getAttributes() { + return attributes; + } + + /** + * Returns the id of this message, set by the server when the message is published. The id is + * guaranteed to be unique within the topic. This value may be read by a subscriber that receives + * a Pub/Sub message via a pull call or a push delivery. If not set, this method returns + * {@code null}. + */ + @Deprecated + public String id() { + return getId(); + } + + /** + * Returns the id of this message, set by the server when the message is published. The id is + * guaranteed to be unique within the topic. This value may be read by a subscriber that receives + * a Pub/Sub message via a pull call or a push delivery. If not set, this method returns + * {@code null}. + */ + public String getId() { + return id; + } + + /** + * Returns the message payload as a string, decoded using {@code UTF-8}. + */ + @Deprecated + public String payloadAsString() { + return getPayloadAsString(); + } + + /** + * Returns the message payload as a string, decoded using {@code UTF-8}. + */ + public String getPayloadAsString() { + return payload.toStringUtf8(); + } + + /** + * Returns the message payload. + */ + @Deprecated + public ByteArray payload() { + return getPayload(); + } + + /** + * Returns the message payload. + */ + public ByteArray getPayload() { + return payload; + } + + final boolean baseEquals(Message message) { + return Objects.equals(id, message.id) + && Objects.equals(payload, message.payload) + && Objects.equals(attributes, message.attributes) + && Objects.equals(publishTime, message.publishTime); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj != null + && obj.getClass().equals(Message.class) + && baseEquals((Message) obj); + } + + @Override + public int hashCode() { + return Objects.hash(id, payload, attributes, publishTime); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", id) + .add("payload", payload) + .add("attributes", attributes) + .add("publishTime", publishTime) + .toString(); + } + + PubsubMessage toPb() { + PubsubMessage.Builder builder = PubsubMessage.newBuilder(); + if (id != null) { + builder.setMessageId(id); + } + builder.setData(payload.getByteString()); + builder.putAllAttributes(attributes); + Timestamp.Builder tsBuilder = Timestamp.newBuilder(); + if (publishTime != null) { + tsBuilder.setSeconds(publishTime / MILLIS_PER_SECOND); + tsBuilder.setNanos((int) (publishTime % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); + } + builder.setPublishTime(tsBuilder); + return builder.build(); + } + + static Message fromPb(PubsubMessage messagePb) { + Builder builder = newBuilder(new InternalByteArray(messagePb.getData())); + if (messagePb.hasPublishTime()) { + Timestamp ts = messagePb.getPublishTime(); + Long millis = ts.getSeconds() * MILLIS_PER_SECOND + ts.getNanos() / NANOS_PER_MILLISECOND; + if (millis != 0) { + builder.setPublishTime(millis); + } + } + if (!Objects.equals(messagePb.getMessageId(), "")) { + builder.setId(messagePb.getMessageId()); + } + for (Map.Entry entry : messagePb.getAttributesMap().entrySet()) { + builder.addAttribute(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + + /** + * Returns a builder for the message object. + */ + public Builder toBuilder() { + return new BuilderImpl(this); + } + + /** + * Creates a {@code Message} object given the payload as a string. The string is enconded using + * {@code UTF-8}. + */ + public static Message of(String payload) { + return newBuilder(payload).build(); + } + + /** + * Creates a {@code Message} object given the payload as a {@link ByteArray}. To be published a + * message must have a non-empty payload. + */ + public static Message of(ByteArray payload) { + return newBuilder(payload).build(); + } + + /** + * Creates a builder for {@code Message} objects given the payload as a string. The string is + * enconded using {@code UTF-8}. To be published a message must have a non-empty payload. + */ + @Deprecated + public static Builder builder(String payload) { + return newBuilder(payload); + } + + /** + * Creates a builder for {@code Message} objects given the payload as a string. The string is + * enconded using {@code UTF-8}. To be published a message must have a non-empty payload. + */ + public static Builder newBuilder(String payload) { + return new BuilderImpl().setPayload(payload); + } + + /** + * Creates a builder for {@code Message} objects given the payload as a {@link ByteArray}. To be + * published a message must have a non-empty payload, or at least one attribute. + */ + @Deprecated + public static Builder builder(ByteArray payload) { + return newBuilder(payload); + } + + /** + * Creates a builder for {@code Message} objects given the payload as a {@link ByteArray}. To be + * published a message must have a non-empty payload, or at least one attribute. + */ + public static Builder newBuilder(ByteArray payload) { + return new BuilderImpl().setPayload(payload); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/MessageConsumerImpl.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/MessageConsumerImpl.java new file mode 100644 index 000000000000..7d8f22ad2acc --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/MessageConsumerImpl.java @@ -0,0 +1,302 @@ +/* + * 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.pubsub; + +import static com.google.common.base.MoreObjects.firstNonNull; + +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.spi.PubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpc.PullCallback; +import com.google.cloud.pubsub.spi.PubSubRpc.PullFuture; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; + +import com.google.pubsub.v1.SubscriptionName; +import io.grpc.internal.SharedResourceHolder; + +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Default implementation for a message consumer. + */ +final class MessageConsumerImpl implements MessageConsumer { + + private static final int MAX_QUEUED_CALLBACKS = 100; + // shared scheduled executor, used to schedule pulls + private static final SharedResourceHolder.Resource CONSUMER_EXECUTOR = + new SharedResourceHolder.Resource() { + @Override + public ExecutorService create() { + return Executors.newSingleThreadExecutor(); + } + + @Override + public void close(ExecutorService instance) { + instance.shutdown(); + } + }; + + private final PubSubOptions pubsubOptions; + private final PubSubRpc pubsubRpc; + private final PubSub pubsub; + private final AckDeadlineRenewer deadlineRenewer; + private final String subscription; + private final MessageProcessor messageProcessor; + private final ExecutorService consumerExecutor; + private final ExecutorFactory executorFactory; + private final ExecutorService executor; + private final AtomicInteger queuedCallbacks; + private final int maxQueuedCallbacks; + private final Object futureLock = new Object(); + private final Runnable consumerRunnable; + private final NextPullPolicy pullPolicy; + private boolean closed; + private Future scheduledFuture; + private PullFuture pullerFuture; + + /** + * Interface for policies according to which the consumer should pull messages. + */ + interface NextPullPolicy { + + boolean shouldPull(int queuedCallbacks); + } + + /** + * Default pull policy. The consumer will pull again once {@code nextPullThreshold} messages out + * of {@code maxQueuedCallbacks} have been processed. + */ + static class DefaultNextPullPolicy implements NextPullPolicy { + + final int maxQueuedCallbacks; + final int nextPullThreshold; + + DefaultNextPullPolicy(int maxQueuedCallbacks, int nextPullThreshold) { + this.maxQueuedCallbacks = maxQueuedCallbacks; + this.nextPullThreshold = nextPullThreshold; + } + + @Override + public boolean shouldPull(int queuedCallbacks) { + return (maxQueuedCallbacks - queuedCallbacks) >= nextPullThreshold; + } + } + + /** + * Default executor factory for the message processor executor. By default a single-threaded + * executor is used. + */ + static class DefaultExecutorFactory implements ExecutorFactory { + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Override + public ExecutorService get() { + return executor; + } + + @Override + public void release(ExecutorService executor) { + executor.shutdownNow(); + } + } + + class ConsumerRunnable implements Runnable { + + @Override + public void run() { + if (closed) { + return; + } + pullerFuture = pubsubRpc.pull(createPullRequest()); + pullerFuture.addCallback(new PullCallback() { + @Override + public void success(PullResponse response) { + List messages = response.getReceivedMessagesList(); + queuedCallbacks.addAndGet(messages.size()); + for (com.google.pubsub.v1.ReceivedMessage message : messages) { + deadlineRenewer.add(subscription, message.getAckId()); + ReceivedMessage receivedMessage = ReceivedMessage.fromPb(pubsub, subscription, message); + executor.execute(ackingRunnable(receivedMessage)); + } + nextPull(); + } + + @Override + public void failure(Throwable error) { + if (!(error instanceof CancellationException)) { + nextPull(); + } + } + }); + } + + private PullRequest createPullRequest() { + return PullRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(pubsubOptions.getProjectId(), subscription)) + .setMaxMessages(maxQueuedCallbacks - queuedCallbacks.get()) + .setReturnImmediately(false) + .build(); + } + + private Runnable ackingRunnable(final ReceivedMessage receivedMessage) { + return new Runnable() { + @Override + public void run() { + try { + messageProcessor.process(receivedMessage); + pubsub.ackAsync(receivedMessage.getSubscription(), receivedMessage.getAckId()); + } catch (Exception ex) { + pubsub.nackAsync(receivedMessage.getSubscription(), receivedMessage.getAckId()); + } finally { + deadlineRenewer.remove(receivedMessage.getSubscription(), receivedMessage.getAckId()); + queuedCallbacks.decrementAndGet(); + // We can now pull more messages, according to the next pull policy. + pullIfNeeded(); + } + } + }; + } + } + + private MessageConsumerImpl(Builder builder) { + this.pubsubOptions = builder.pubsubOptions; + this.subscription = builder.subscription; + this.messageProcessor = builder.messageProcessor; + this.pubsubRpc = pubsubOptions.getRpc(); + this.pubsub = pubsubOptions.getService(); + this.deadlineRenewer = builder.deadlineRenewer; + this.queuedCallbacks = new AtomicInteger(); + this.consumerExecutor = SharedResourceHolder.get(CONSUMER_EXECUTOR); + this.executorFactory = + builder.executorFactory != null ? builder.executorFactory : new DefaultExecutorFactory(); + this.executor = executorFactory.get(); + this.maxQueuedCallbacks = firstNonNull(builder.maxQueuedCallbacks, MAX_QUEUED_CALLBACKS); + this.consumerRunnable = new ConsumerRunnable(); + int nextPullThreshold = builder.nextPullThreshold != null ? builder.nextPullThreshold + : this.maxQueuedCallbacks / 2; + this.pullPolicy = new DefaultNextPullPolicy(maxQueuedCallbacks, nextPullThreshold); + nextPull(); + } + + private void pullIfNeeded() { + synchronized (futureLock) { + if (closed || scheduledFuture != null || !pullPolicy.shouldPull(queuedCallbacks.get())) { + return; + } + scheduledFuture = consumerExecutor.submit(consumerRunnable); + } + } + + private void nextPull() { + synchronized (futureLock) { + if (closed || queuedCallbacks.get() == maxQueuedCallbacks) { + scheduledFuture = null; + return; + } + scheduledFuture = consumerExecutor.submit(consumerRunnable); + } + } + + @Override + public void close() { + synchronized (futureLock) { + if (closed) { + return; + } + closed = true; + if (scheduledFuture != null) { + scheduledFuture.cancel(true); + } + if (pullerFuture != null) { + pullerFuture.cancel(true); + } + } + SharedResourceHolder.release(CONSUMER_EXECUTOR, consumerExecutor); + executorFactory.release(executor); + } + + static final class Builder { + private final PubSubOptions pubsubOptions; + private final String subscription; + private final AckDeadlineRenewer deadlineRenewer; + private final MessageProcessor messageProcessor; + private Integer maxQueuedCallbacks; + private ExecutorFactory executorFactory; + private Integer nextPullThreshold; + + Builder(PubSubOptions pubsubOptions, String subscription, AckDeadlineRenewer deadlineRenewer, + MessageProcessor messageProcessor) { + this.pubsubOptions = pubsubOptions; + this.subscription = subscription; + this.deadlineRenewer = deadlineRenewer; + this.messageProcessor = messageProcessor; + } + + /** + * Sets the maximum number of callbacks either being executed or waiting for execution. + */ + Builder maxQueuedCallbacks(Integer maxQueuedCallbacks) { + this.maxQueuedCallbacks = maxQueuedCallbacks; + return this; + } + + /** + * Sets the executor factory, used to manage the executor that will run message processor + * callbacks message consumer. + */ + Builder executorFactory(ExecutorFactory executorFactory) { + this.executorFactory = executorFactory; + return this; + } + + /** + * Sets a threshold for the next pull. If the consumer stopped pulling due to reaching the + * maximum number of queued callbacks, it will be pull again only once at least + * {@code nextPullThreshold} callbacks have completed their execution. + */ + Builder nextPullThreshold(Integer nextPullThreshold) { + this.nextPullThreshold = nextPullThreshold; + return this; + } + + /** + * Creates a {@code MessageConsumerImpl} object. + */ + MessageConsumerImpl build() { + return new MessageConsumerImpl(this); + } + } + + /** + * Returns a builder for {@code MessageConsumerImpl} objects given the service options, the + * subscription from which messages must be pulled, the acknowledge deadline renewer and a message + * processor used to process messages. + */ + static Builder builder(PubSubOptions pubsubOptions, String subscription, + AckDeadlineRenewer deadlineRenewer, MessageProcessor messageProcessor) { + return new Builder(pubsubOptions, subscription, deadlineRenewer, messageProcessor); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Option.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Option.java new file mode 100644 index 000000000000..b64dc80733d4 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Option.java @@ -0,0 +1,77 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Base class for Pub/Sub operation options. + */ +abstract class Option implements Serializable { + + private static final long serialVersionUID = 4956295408130172192L; + + private final OptionType optionType; + private final Object value; + + interface OptionType { + + String name(); + } + + Option(OptionType optionType, Object value) { + this.optionType = checkNotNull(optionType); + this.value = value; + } + + @SuppressWarnings("unchecked") + T getOptionType() { + return (T) optionType; + } + + Object getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Option)) { + return false; + } + Option other = (Option) obj; + return Objects.equals(optionType, other.optionType) + && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(optionType, value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", optionType.name()) + .add("value", value) + .toString(); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PolicyMarshaller.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PolicyMarshaller.java new file mode 100644 index 000000000000..5e910db00dd6 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PolicyMarshaller.java @@ -0,0 +1,36 @@ +/* + * 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.pubsub; + +import com.google.cloud.Policy; + +final class PolicyMarshaller extends Policy.DefaultMarshaller { + + static final PolicyMarshaller INSTANCE = new PolicyMarshaller(); + + private PolicyMarshaller() {} + + @Override + protected com.google.iam.v1.Policy toPb(Policy policy) { + return super.toPb(policy); + } + + @Override + protected Policy fromPb(com.google.iam.v1.Policy policyPb) { + return super.fromPb(policyPb); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSub.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSub.java new file mode 100644 index 000000000000..3e45fce3eb11 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSub.java @@ -0,0 +1,1446 @@ +/* + * 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.pubsub; + +import com.google.cloud.AsyncPage; +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.Page; +import com.google.cloud.Policy; +import com.google.cloud.Service; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * An interface for Google Cloud Pub/Sub. + * + * @see Google Cloud Pub/Sub + */ +public interface PubSub extends AutoCloseable, Service { + + /** + * Class for specifying options for listing topics and subscriptions. + */ + final class ListOption extends Option { + + private static final long serialVersionUID = 6517442127283383124L; + + enum OptionType implements Option.OptionType { + PAGE_SIZE, PAGE_TOKEN; + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + + String getString(Map options) { + return get(options); + } + + Integer getInteger(Map options) { + return get(options); + } + } + + private ListOption(OptionType option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the maximum number of resources returned per page. + */ + public static ListOption pageSize(int pageSize) { + return new ListOption(OptionType.PAGE_SIZE, pageSize); + } + + /** + * Returns an option to specify the page token from which to start listing resources. + */ + public static ListOption pageToken(String pageToken) { + return new ListOption(OptionType.PAGE_TOKEN, pageToken); + } + } + + /** + * Class for specifying options for pulling messages. + */ + final class PullOption extends Option { + + private static final long serialVersionUID = 4792164134340316582L; + + enum OptionType implements Option.OptionType { + EXECUTOR_FACTORY, + MAX_QUEUED_CALLBACKS; + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + + Integer getInteger(Map options) { + return get(options); + } + + ExecutorFactory getExecutorFactory(Map options) { + return get(options); + } + } + + private PullOption(Option.OptionType option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the maximum number of messages that can be queued in the message + * consumer at any time. Queued messages are already pulled messages that are either waiting to + * be processed or being processed. Queued messages will have their acknowledge deadline renewed + * until they are acknowledged or "nacked". If not provided, at most 100 messages can be in the + * queue. + */ + public static PullOption maxQueuedCallbacks(int maxQueuedCallbacks) { + return new PullOption(OptionType.MAX_QUEUED_CALLBACKS, maxQueuedCallbacks); + } + + /** + * Returns an option to specify the executor used to execute message processor callbacks. The + * executor determines the number of messages that can be processed at the same time. If not + * provided, a single-threaded executor is used to execute message processor callbacks. + * + *

    The {@link ExecutorFactory} object can be used to handle creation and release of the + * executor, possibly reusing existing executors. {@link ExecutorFactory#get()} is called when + * the message consumer is created. {@link ExecutorFactory#release(ExecutorService)} is called + * when the message consumer is closed. + * + *

    For the created option to be serializable, the provided executor factory should implement + * {@link java.io.Serializable}. + * + * @param executorFactory the executor factory. + */ + public static PullOption executorFactory(ExecutorFactory executorFactory) { + return new PullOption(OptionType.EXECUTOR_FACTORY, executorFactory); + } + } + + /** + * A callback to process pulled messages. The received message will be ack'ed upon successful + * return or nack'ed if exception is thrown. + */ + interface MessageProcessor { + /** + * Processes the received {@code message}. If this method returns correctly the message is + * ack'ed. If this method throws an exception the message is nack'ed. + */ + void process(Message message) throws Exception; + } + + /** + * An interface to control a message consumer. + */ + interface MessageConsumer extends AutoCloseable { + + /** + * Stops pulling messages from the subscription associated with this {@code MessageConsumer} and + * frees all resources. Messages that have already been pulled are processed before closing. + */ + @Override + void close() throws Exception; + } + + /** + * Creates a new topic. + * + *

    Example of creating a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * TopicInfo topicInfo = TopicInfo.of(topicName);
    +   * Topic topic = pubsub.create(topicInfo);
    +   * }
    + * + * @return the created topic + * @throws PubSubException upon failure + */ + Topic create(TopicInfo topic); + + /** + * Sends a request for creating a topic. This method returns a {@code Future} object to consume + * the result. {@link Future#get()} returns the created topic. + * + *

    Example of asynchronously creating a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * TopicInfo topicInfo = TopicInfo.of(topicName);
    +   * Future future = pubsub.createAsync(topicInfo);
    +   * // ...
    +   * Topic topic = future.get();
    +   * }
    + * + */ + Future createAsync(TopicInfo topic); + + /** + * Returns the requested topic or {@code null} if not found. + * + *

    Example of getting a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Topic topic = pubsub.getTopic(topicName);
    +   * if (topic == null) {
    +   *   // topic was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Topic getTopic(String topic); + + /** + * Sends a request for getting a topic. This method returns a {@code Future} object to consume the + * result. {@link Future#get()} returns the requested topic or {@code null} if not found. + * + *

    Example of asynchronously getting a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Future future = pubsub.getTopicAsync(topicName);
    +   * // ...
    +   * Topic topic = future.get();
    +   * if (topic == null) {
    +   *   // topic was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future getTopicAsync(String topic); + + /** + * Deletes the requested topic. + * + *

    Example of deleting a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * boolean deleted = pubsub.deleteTopic(topicName);
    +   * 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 + */ + boolean deleteTopic(String topic); + + /** + * Sends a request for deleting a topic. This method returns a {@code Future} object to consume + * the result. {@link Future#get()} returns {@code true} if the topic was deleted, {@code false} + * if it was not found. + * + *

    Example of asynchronously deleting a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Future future = pubsub.deleteTopicAsync(topicName);
    +   * // ...
    +   * boolean deleted = future.get();
    +   * if (deleted) {
    +   *   // the topic was deleted
    +   * } else {
    +   *   // the topic was not found
    +   * }
    +   * }
    + * + */ + Future deleteTopicAsync(String topic); + + /** + * Lists the topics. This method returns a {@link Page} 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 topics. + * + *

    Example of listing topics, specifying the page size. + *

     {@code
    +   * Page topics = pubsub.listTopics(ListOption.pageSize(100));
    +   * Iterator topicIterator = topics.iterateAll();
    +   * while (topicIterator.hasNext()) {
    +   *   Topic topic = topicIterator.next();
    +   *   // do something with the topic
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Page listTopics(ListOption... options); + + /** + * Sends a request for listing topics. This method returns a {@code Future} object to consume + * the result. {@link Future#get()} returns an {@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 topics. + * + *

    Example of asynchronously listing topics, specifying the page size. + *

     {@code
    +   * Future> future = pubsub.listTopicsAsync(ListOption.pageSize(100));
    +   * // ...
    +   * AsyncPage topics = future.get();
    +   * Iterator topicIterator = topics.iterateAll();
    +   * while (topicIterator.hasNext()) {
    +   *   Topic topic = topicIterator.next();
    +   *   // do something with the topic
    +   * }
    +   * }
    + * + */ + Future> listTopicsAsync(ListOption... options); + + /** + * Publishes a message to the provided 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 a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Message message = Message.of("payload");
    +   * String messageId = pubsub.publish(topicName, message);
    +   * }
    + * + * @param topic the topic where the message is published + * @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 + * payload and no attributes + */ + String publish(String topic, Message message); + + /** + * Sends a request for publishing a message to the provided topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a service-generated + * id for the published message. Service-generated ids are guaranteed to be unique within the + * topic. + * + *

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

     {@code
    +   * String topicName = "my_topic_name";
    +   * Message message = Message.of("payload");
    +   * Future future = pubsub.publishAsync(topicName, message);
    +   * // ...
    +   * String messageId = future.get();
    +   * }
    + * + * @param topic the topic where the message is published + * @param message the message to publish + * @return a {@code Future} for the unique service-generated id for the message + */ + Future publishAsync(String topic, Message message); + + /** + * Publishes a number of messages to the provided topic. This method returns a list of + * service-generated ids for the published messages. Service-generated ids are guaranteed to be + * unique within the topic. + * + *

    Example of publishing some messages to a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Message message1 = Message.of("payload1");
    +   * Message message2 = Message.of("payload2");
    +   * List messageIds = pubsub.publish(topicName, message1, message2);
    +   * }
    + * + * @param topic the topic where the message is published + * @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. + * @throws PubSubException upon failure, if the topic does not exist or if one of the messages has + * empty payload and no attributes + */ + List publish(String topic, Message message, Message... messages); + + /** + * Sends a request to publish a number of messages to the provided topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a list of + * 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 a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Message message1 = Message.of("payload1");
    +   * Message message2 = Message.of("payload2");
    +   * Future> future = pubsub.publishAsync(topicName, message1, message2);
    +   * // ...
    +   * List messageIds = future.get();
    +   * }
    + * + * @param topic the topic where the message is published + * @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 + * the messages. + */ + Future> publishAsync(String topic, Message message, Message... messages); + + /** + * Publishes a number of messages to the provided topic. This method returns a list of + * service-generated ids for the published messages. Service-generated ids are guaranteed to be + * unique within the topic. + * + *

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

     {@code
    +   * String topicName = "my_topic_name";
    +   * List messages = new LinkedList<>();
    +   * messages.add(Message.of("payload1"));
    +   * messages.add(Message.of("payload2"));
    +   * List messageIds = pubsub.publish(topicName, messages);
    +   * }
    + * + * @param topic the topic where the message is published + * @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 + * empty payload and no attributes + */ + List publish(String topic, Iterable messages); + + /** + * Sends a request to publish a number of messages to the provided topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a list of + * 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 a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * List messages = new LinkedList<>();
    +   * messages.add(Message.of("payload1"));
    +   * messages.add(Message.of("payload2"));
    +   * Future> future = pubsub.publishAsync(topicName, messages);
    +   * // ...
    +   * List messageIds = future.get();
    +   * }
    + * + * @param topic the topic where the message is published + * @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 + */ + Future> publishAsync(String topic, Iterable messages); + + /** + * Creates a new subscription. + * + *

    Example of creating a pull subscription for a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * String subscriptionName = "my_subscription_name";
    +   * SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(topicName, subscriptionName);
    +   * Subscription subscription = pubsub.create(subscriptionInfo);
    +   * }
    + * + * @return the created subscription + * @throws PubSubException upon failure + */ + Subscription create(SubscriptionInfo subscription); + + /** + * Sends a request for creating a subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns the created subscription. + * + *

    Example of asynchronously creating a pull subscription for a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * String subscriptionName = "my_subscription_name";
    +   * SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(topicName, subscriptionName);
    +   * Future future = pubsub.createAsync(subscriptionInfo);
    +   * // ...
    +   * Subscription subscription = future.get();
    +   * }
    + * + */ + Future createAsync(SubscriptionInfo subscription); + + /** + * Returns the requested subscription or {@code null} if not found. + * + *

    Example of getting a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Subscription subscription = pubsub.getSubscription(subscriptionName);
    +   * if (subscription == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + */ + Subscription getSubscription(String subscription); + + /** + * Sends a request for getting a subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns the requested subscription or {@code null} if + * not found. + * + *

    Example of asynchronously getting a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Future future = pubsub.getSubscriptionAsync(subscriptionName);
    +   * // ...
    +   * Subscription subscription = future.get();
    +   * if (subscription == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + */ + Future getSubscriptionAsync(String subscription); + + /** + * Sets the push configuration for a specified subscription. This may be used to change a push + * subscription to a pull one (passing a {@code null} {@code pushConfig} parameter) or vice versa. + * This methods can also be used to change the endpoint URL and other attributes of a push + * subscription. Messages will accumulate for delivery regardless of changes to the push + * configuration. + * + *

    Example of replacing the push configuration of a subscription, setting the push endpoint. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String endpoint = "https://www.example.com/push";
    +   * PushConfig pushConfig = PushConfig.of(endpoint);
    +   * pubsub.replacePushConfig(subscriptionName, pushConfig);
    +   * }
    + * + *

    Example of replacing the push configuration of a subscription, making it a pull + * subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * pubsub.replacePushConfig(subscriptionName, null);
    +   * }
    + * + * @param subscription the subscription for which to replace push configuration + * @param pushConfig the new push configuration. Use {@code null} to unset it + * @throws PubSubException upon failure, or if the subscription does not exist + */ + void replacePushConfig(String subscription, PushConfig pushConfig); + + /** + * Sends a request for updating the push configuration for a specified subscription. This may be + * used to change a push subscription to a pull one (passing a {@code null} {@code pushConfig} + * parameter) or vice versa. This methods can also be used to change the endpoint URL and other + * attributes of a push subscription. Messages will accumulate for delivery regardless of changes + * to the push configuration. The method returns a {@code Future} object that can be used to wait + * for the replace operation to be completed. + * + *

    Example of asynchronously replacing the push configuration of a subscription, setting the + * push endpoint. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String endpoint = "https://www.example.com/push";
    +   * PushConfig pushConfig = PushConfig.of(endpoint);
    +   * Future future = pubsub.replacePushConfigAsync(subscriptionName, pushConfig);
    +   * // ...
    +   * future.get();
    +   * }
    + * + *

    Example of asynchronously replacing the push configuration of a subscription, making it a + * pull subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Future future = pubsub.replacePushConfigAsync(subscriptionName, null);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription for which to replace push configuration + * @param pushConfig the new push configuration. Use {@code null} to unset it + * @return a {@code Future} to wait for the replace operation to be completed. + */ + Future replacePushConfigAsync(String subscription, PushConfig pushConfig); + + /** + * Deletes the requested subscription. + * + *

    Example of deleting a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * boolean deleted = pubsub.deleteSubscription(subscriptionName);
    +   * if (deleted) {
    +   *   // the subscription was deleted
    +   * } else {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + * @return {@code true} if the subscription was deleted, {@code false} if it was not found + * @throws PubSubException upon failure + */ + boolean deleteSubscription(String subscription); + + /** + * Sends a request for deleting a subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns {@code true} if the subscription was deleted, + * {@code false} if it was not found. + * + *

    Example of asynchronously deleting a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Future future = pubsub.deleteSubscriptionAsync(subscriptionName);
    +   * // ...
    +   * boolean deleted = future.get();
    +   * if (deleted) {
    +   *   // the subscription was deleted
    +   * } else {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + */ + Future deleteSubscriptionAsync(String subscription); + + /** + * Lists the subscriptions. This method returns a {@link Page} 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, specifying the page size. + *

     {@code
    +   * Page subscriptions = pubsub.listSubscriptions(ListOption.pageSize(100));
    +   * Iterator subscriptionIterator = subscriptions.iterateAll();
    +   * while (subscriptionIterator.hasNext()) {
    +   *   Subscription subscription = subscriptionIterator.next();
    +   *   // do something with the subscription
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Page listSubscriptions(ListOption... options); + + /** + * Sends a request for listing subscriptions. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns an {@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, specifying the page size. + *

     {@code
    +   * Future> future =
    +   *     pubsub.listSubscriptionsAsync(ListOption.pageSize(100));
    +   * // ...
    +   * AsyncPage subscriptions = future.get();
    +   * Iterator subscriptionIterator = subscriptions.iterateAll();
    +   * while (subscriptionIterator.hasNext()) {
    +   *   Subscription subscription = subscriptionIterator.next();
    +   *   // do something with the subscription
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future> listSubscriptionsAsync(ListOption... options); + + /** + * Lists the identities of the subscriptions for the provided topic. This method returns a + * {@link Page} 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 a topic, specifying the page size. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Page subscriptions =
    +   *     pubsub.listSubscriptions(topicName, ListOption.pageSize(100));
    +   * Iterator subscriptionIterator = subscriptions.iterateAll();
    +   * while (subscriptionIterator.hasNext()) {
    +   *   SubscriptionId subscription = subscriptionIterator.next();
    +   *   // do something with the subscription identity
    +   * }
    +   * }
    + * + * @param topic the topic for which to list subscriptions + * @throws PubSubException upon failure + */ + Page listSubscriptions(String topic, ListOption... options); + + /** + * Sends a request for listing the identities of subscriptions for the provided topic. This method + * returns a {@code Future} object to consume the result. {@link Future#get()} returns an + * {@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 a topic, specifying the page size. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Future> future =
    +   *     pubsub.listSubscriptionsAsync(topicName, ListOption.pageSize(100));
    +   * // ...
    +   * AsyncPage subscriptions = future.get();
    +   * Iterator subscriptionIterator = subscriptions.iterateAll();
    +   * while (subscriptionIterator.hasNext()) {
    +   *   SubscriptionId subscription = subscriptionIterator.next();
    +   *   // do something with the subscription identity
    +   * }
    +   * }
    + * + * @param topic the topic for which to list subscriptions + */ + Future> listSubscriptionsAsync(String topic, ListOption... options); + + /** + * Pulls messages from the provided subscription. This method possibly returns no messages if no + * message was available at the time the request was processed by the Pub/Sub service (i.e. the + * system is not allowed to wait until at least one message is available - + * return_immediately + * option is set to {@code true}). Pulled messages have their acknowledge deadline automatically + * renewed until they are explicitly consumed using {@link Iterator#next()}. + * + *

    Example of pulling a maximum number of messages from a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Iterator messages = pubsub.pull(subscriptionName, 100);
    +   * // Ack deadline is renewed until the message is consumed
    +   * while (messages.hasNext()) {
    +   *   ReceivedMessage message = messages.next();
    +   *   // do something with message and ack/nack it
    +   *   message.ack(); // or message.nack()
    +   * }
    +   * }
    + * + * @param subscription the subscription from which to pull messages + * @param maxMessages the maximum number of messages pulled by this method. This method can + * possibly return fewer messages. + * @throws PubSubException upon failure + */ + Iterator pull(String subscription, int maxMessages); + + /** + * Sends a request for pulling messages from the provided subscription. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a message iterator. + * When using this method the system is allowed to wait until at least one message is available + * rather than returning no messages (i.e. + * return_immediately + * option is set to {@code false}). The client may cancel the request by calling + * {@link Future#cancel(boolean)} if it does not wish to wait any longer. Notice that the Pub/Sub + * service might still return no messages if a timeout is reached on the service side. + * + *

    Example of asynchronously pulling a maximum number of messages from a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Future> future = pubsub.pullAsync(subscriptionName, 100);
    +   * // ...
    +   * Iterator messages = future.get();
    +   * // Ack deadline is renewed until the message is consumed
    +   * while (messages.hasNext()) {
    +   *   ReceivedMessage message = messages.next();
    +   *   // do something with message and ack/nack it
    +   *   message.ack(); // or message.nack()
    +   * }
    +   * }
    + * + * @param subscription the subscription from which to pull messages + * @param maxMessages the maximum number of messages pulled by this method. This method can + * possibly return fewer messages. + * @throws PubSubException upon failure + */ + Future> pullAsync(String subscription, int maxMessages); + + /** + * Creates a message consumer that pulls messages from the provided subscription. You can stop + * pulling messages by calling {@link MessageConsumer#close()}. The returned message consumer + * executes {@link MessageProcessor#process(Message)} on each pulled message. If + * {@link MessageProcessor#process(Message)} executes correctly, the message is acknowledged. If + * {@link MessageProcessor#process(Message)} throws an exception, the message is "nacked". For + * all pulled messages, the ack deadline is automatically renewed until the message is either + * acknowledged or "nacked". + * + *

    The {@link PullOption#maxQueuedCallbacks(int)} option can be used to control the maximum + * number of queued messages (messages either being processed or waiting to be processed). The + * {@link PullOption#executorFactory(ExecutorFactory)} can be used to provide an executor to run + * message processor callbacks. + * + *

    Example of continuously pulling messages from a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * MessageProcessor callback = new MessageProcessor() {
    +   *   public void process(Message message) throws Exception {
    +   *     // Ack deadline is renewed until this method returns
    +   *     // Message is acked if this method returns successfully
    +   *     // Message is nacked if this method throws an exception
    +   *   }
    +   * };
    +   * PubSub.MessageConsumer consumer = pubsub.pullAsync(subscriptionName, callback);
    +   * // ...
    +   * // Stop pulling
    +   * consumer.close();
    +   * }
    + * + * @param subscription the subscription from which to pull messages + * @param callback the callback to be executed on each message + * @param options pulling options + * @return a message consumer for the provided subscription and options + */ + MessageConsumer pullAsync(String subscription, MessageProcessor callback, PullOption... options); + + /** + * Acknowledges the given messages for the provided subscription. Ack ids identify the messages to + * acknowledge, as returned in {@link ReceivedMessage#ackId()} by {@link #pull(String, int)} and + * {@link #pullAsync(String, int)}. + * + *

    Example of acking one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * pubsub.ack(subscriptionName, ackId);
    +   * }
    + * + *

    Example of acking more messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * pubsub.ack(subscriptionName, ackId1, ackId2);
    +   * }
    + * + * @param subscription the subscription whose messages must be acknowledged + * @param ackId the ack id of the first message to acknowledge + * @param ackIds other ack ids of messages to acknowledge + * @throws PubSubException upon failure, or if the subscription was not found + */ + void ack(String subscription, String ackId, String... ackIds); + + /** + * Sends a request to acknowledge the given messages for the provided subscription. Ack ids + * identify the messages to acknowledge, as returned in {@link ReceivedMessage#ackId()} by + * {@link #pull(String, int)} and {@link #pullAsync(String, int)}. The method returns a + * {@code Future} object that can be used to wait for the acknowledge operation to be completed. + * + *

    Example of asynchronously acking one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * Future future = pubsub.ackAsync(subscriptionName, ackId);
    +   * // ...
    +   * future.get();
    +   * }
    + * + *

    Example of asynchronously acking more messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * Future future = pubsub.ackAsync(subscriptionName, ackId1, ackId2);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages must be acknowledged + * @param ackId the ack id of the first message to acknowledge + * @param ackIds other ack ids of messages to acknowledge + */ + Future ackAsync(String subscription, String ackId, String... ackIds); + + /** + * Acknowledges the given messages for the provided subscription. Ack ids identify the messages to + * acknowledge, as returned in {@link ReceivedMessage#ackId()} by {@link #pull(String, int)} and + * {@link #pullAsync(String, int)}. + * + *

    Example of acking a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * pubsub.ack(subscriptionName, ackIds);
    +   * }
    + * + * @param subscription the subscription whose messages must be acknowledged + * @param ackIds the ack ids of messages to acknowledge + * @throws PubSubException upon failure, or if the subscription was not found + */ + void ack(String subscription, Iterable ackIds); + + /** + * Sends a request to acknowledge the given messages for the provided subscription. Ack ids + * identify the messages to acknowledge, as returned in {@link ReceivedMessage#ackId()} by + * {@link #pull(String, int)} and {@link #pullAsync(String, int)}. The method returns a + * {@code Future} object that can be used to wait for the acknowledge operation to be completed. + * + *

    Example of asynchronously acking a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * Future future = pubsub.ackAsync(subscriptionName, ackIds);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages must be acknowledged + * @param ackIds the ack ids of messages to acknowledge + */ + Future ackAsync(String subscription, Iterable ackIds); + + /** + * "Nacks" the given messages for the provided subscription. Ack ids identify the messages to + * "nack", as returned in {@link ReceivedMessage#ackId()} by {@link #pull(String, int)} and + * {@link #pullAsync(String, int)}. This method corresponds to calling + * {@link #modifyAckDeadline(String, int, TimeUnit, String, String...)} with a deadline of 0. + * + *

    Example of nacking one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * pubsub.nack(subscriptionName, ackId);
    +   * }
    + * + *

    Example of nacking more messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * pubsub.nack(subscriptionName, ackId1, ackId2);
    +   * }
    + * + * @param subscription the subscription whose messages must be "nacked" + * @param ackId the ack id of the first message to "nack" + * @param ackIds other ack ids of messages to "nack" + * @throws PubSubException upon failure, or if the subscription was not found + */ + void nack(String subscription, String ackId, String... ackIds); + + /** + * Sends a request to "nack" the given messages for the provided subscription. Ack ids identify + * the messages to "nack", as returned in {@link ReceivedMessage#ackId()} by + * {@link #pull(String, int)} and {@link #pullAsync(String, int)}. This method corresponds to + * calling {@link #modifyAckDeadlineAsync(String, int, TimeUnit, String, String...)} with a + * deadline of 0. The method returns a {@code Future} object that can be used to wait for the + * "nack" operation to be completed. + * + *

    Example of asynchronously nacking one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * Future future = pubsub.nackAsync(subscriptionName, ackId);
    +   * // ...
    +   * future.get();
    +   * }
    + * + *

    Example of asynchronously nacking more messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * Future future = pubsub.nackAsync(subscriptionName, ackId1, ackId2);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages must be "nacked" + * @param ackId the ack id of the first message to "nack" + * @param ackIds other ack ids of messages to "nack" + */ + Future nackAsync(String subscription, String ackId, String... ackIds); + + /** + * "Nacks" the given messages for the provided subscription. Ack ids identify the messages to + * "nack", as returned in {@link ReceivedMessage#ackId()} by {@link #pull(String, int)} and + * {@link #pullAsync(String, int)}. This method corresponds to calling + * {@link #modifyAckDeadline(String, int, TimeUnit, Iterable)} with a deadline of 0. + * + *

    Example of nacking a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * pubsub.nack(subscriptionName, ackIds);
    +   * }
    + * + * @param subscription the subscription whose messages must be "nacked" + * @param ackIds the ack ids of messages to "nack" + * @throws PubSubException upon failure, or if the subscription was not found + */ + void nack(String subscription, Iterable ackIds); + + /** + * Sends a request to "nack" the given messages for the provided subscription. Ack ids identify + * the messages to "nack", as returned in {@link ReceivedMessage#ackId()} by + * {@link #pull(String, int)} and {@link #pullAsync(String, int)}. This method corresponds to + * calling {@link #modifyAckDeadlineAsync(String, int, TimeUnit, Iterable)} with a deadline of 0. + * The method returns a {@code Future} object that can be used to wait for the "nack" operation to + * be completed. + * + *

    Example of asynchronously nacking a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * Future future = pubsub.nackAsync(subscriptionName, ackIds);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages must be "nacked" + * @param ackIds the ack ids of messages to "nack" + */ + Future nackAsync(String subscription, Iterable ackIds); + + /** + * Modifies the acknowledge deadline of the given messages. {@code deadline} must be >= 0 and + * is the new deadline with respect to the time the modify request was received by the Pub/Sub + * service. For example, if {@code deadline} is 10 and {@code unit} is {@link TimeUnit#SECONDS}, + * the new ack deadline will expire 10 seconds after the modify request was received by the + * service. Specifying 0 may be used to make the message available for another pull request + * (corresponds to calling {@link #nack(String, String, String...)}). + * + *

    Example of modifying the ack deadline of one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackId);
    +   * }
    + * + *

    Example of modifying the ack deadline of some messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackId1, ackId2);
    +   * }
    + * + * @param subscription the subscription whose messages need to update their acknowledge deadline + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit time unit for the {@code deadline} parameter + * @param ackId the ack id of the first message for which the acknowledge deadline must be + * modified + * @param ackIds other ack ids of messages for which the acknowledge deadline must be modified + * @throws PubSubException upon failure, or if the subscription was not found + */ + void modifyAckDeadline(String subscription, int deadline, TimeUnit unit, String ackId, + String... ackIds); + + /** + * Sends a request to modify the acknowledge deadline of the given messages. {@code deadline} + * must be >= 0 and is the new deadline with respect to the time the modify request was + * received by the Pub/Sub service. For example, if {@code deadline} is 10 and {@code unit} is + * {@link TimeUnit#SECONDS}, the new ack deadline will expire 10 seconds after the modify request + * was received by the service. Specifying 0 may be used to make the message available for another + * pull request (corresponds to calling {@link #nackAsync(String, Iterable)}). The method returns + * a {@code Future} object that can be used to wait for the modify operation to be completed. + * + *

    Example of asynchronously modifying the ack deadline of one message. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId = "message_ack_id";
    +   * Future future =
    +   *     pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackId);
    +   * // ...
    +   * future.get();
    +   * }
    + * + *

    Example of asynchronously modifying the ack deadline of some messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * Future future =
    +   *     pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackId1, ackId2);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages need to update their acknowledge deadline + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit time unit for the {@code deadline} parameter + * @param ackId the ack id of the first message for which the acknowledge deadline must be + * modified + * @param ackIds other ack ids of messages for which the acknowledge deadline must be modified + */ + Future modifyAckDeadlineAsync(String subscription, int deadline, TimeUnit unit, + String ackId, String... ackIds); + + /** + * Modifies the acknowledge deadline of the given messages. {@code deadline} must be >= 0 and + * is the new deadline with respect to the time the modify request was received by the Pub/Sub + * service. For example, if {@code deadline} is 10 and {@code unit} is {@link TimeUnit#SECONDS}, + * the new ack deadline will expire 10 seconds after the modify request was received by the + * service. Specifying 0 may be used to make the message available for another pull request + * (corresponds to calling {@link #nack(String, Iterable)}). + * + *

    Example of modifying the ack deadline of a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * pubsub.modifyAckDeadline(subscriptionName, 60, TimeUnit.SECONDS, ackIds);
    +   * }
    + * + * @param subscription the subscription whose messages need to update their acknowledge deadline + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit time unit for the {@code deadline} parameter + * @param ackIds the ack ids of messages for which the acknowledge deadline must be modified + * @throws PubSubException upon failure, or if the subscription was not found + */ + void modifyAckDeadline(String subscription, int deadline, TimeUnit unit, Iterable ackIds); + + /** + * Sends a request to modify the acknowledge deadline of the given messages. {@code deadline} + * must be >= 0 and is the new deadline with respect to the time the modify request was + * received by the Pub/Sub service. For example, if {@code deadline} is 10 and {@code unit} is + * {@link TimeUnit#SECONDS}, the new ack deadline will expire 10 seconds after the modify request + * was received by the service. Specifying 0 may be used to make the message available for another + * pull request (corresponds to calling {@link #nackAsync(String, Iterable)}). The method returns + * a {@code Future} object that can be used to wait for the modify operation to be completed. + * + *

    Example of asynchronously modifying the ack deadline of a list of messages. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * String ackId1 = "message1_ack_id";
    +   * String ackId2 = "message2_ack_id";
    +   * List ackIds = new LinkedList<>();
    +   * ackIds.add(ackId1);
    +   * ackIds.add(ackId2);
    +   * Future future =
    +   *     pubsub.modifyAckDeadlineAsync(subscriptionName, 60, TimeUnit.SECONDS, ackIds);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param subscription the subscription whose messages need to update their acknowledge deadline + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit time unit for the {@code deadline} parameter + * @param ackIds the ack ids of messages for which the acknowledge deadline must be modified + */ + Future modifyAckDeadlineAsync(String subscription, int deadline, TimeUnit unit, + Iterable ackIds); + + /** + * Returns the IAM access control policy for the specified topic. Returns {@code null} if the + * topic was not found. + * + *

    Example of getting a topic policy. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Policy policy = pubsub.getTopicPolicy(topicName);
    +   * if (policy == null) {
    +   *   // topic was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Policy getTopicPolicy(String topic); + + /** + * Sends a request for getting the IAM access control policy for the specified topic. This method + * returns a {@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 a topic policy. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Future future = pubsub.getTopicPolicyAsync(topicName);
    +   * // ...
    +   * Policy policy = future.get();
    +   * if (policy == null) {
    +   *   // topic was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future getTopicPolicyAsync(String topic); + + /** + * Sets the IAM access control policy for the specified topic. Replaces any existing policy. This + * method returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, a + * {@code PubSubException} is thrown, denoting that the server aborted update. If an etag is not + * provided, the policy is overwritten blindly. + * + *

    Example of replacing a topic policy. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Policy policy = pubsub.getTopicPolicy(topicName);
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * updatedPolicy = pubsub.replaceTopicPolicy(topicName, updatedPolicy);
    +   * }
    + * + * @throws PubSubException upon failure + */ + Policy replaceTopicPolicy(String topic, Policy newPolicy); + + /** + * Sends a request to set the IAM access control policy for the specified topic. Replaces any + * existing policy. This method returns a {@code Future} object to consume the result. + * {@link Future#get()} returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, + * {@link Future#get()} will throw a {@link java.util.concurrent.ExecutionException} caused by a + * {@code PubSubException}, denoting that the server aborted update. If an etag is not provided, + * the policy is overwritten blindly. + * + *

    Example of asynchronously replacing a topic policy. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * Policy policy = pubsub.getTopicPolicy(topicName);
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * Future future = pubsub.replaceTopicPolicyAsync(topicName, updatedPolicy);
    +   * // ...
    +   * updatedPolicy = future.get();
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future replaceTopicPolicyAsync(String topic, Policy newPolicy); + + /** + * Returns the permissions that a caller has on the specified topic. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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 a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.topics.get");
    +   * List testedPermissions = pubsub.testTopicPermissions(topicName, 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 + * @see + * Permissions and Roles + */ + List testTopicPermissions(String topic, List permissions); + + /** + * Sends a request to get the permissions that a caller has on the specified topic. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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 a topic. + *

     {@code
    +   * String topicName = "my_topic_name";
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.topics.get");
    +   * Future> future = pubsub.testTopicPermissionsAsync(topicName, 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) + * @throws PubSubException upon failure + * @see + * Permissions and Roles + */ + Future> testTopicPermissionsAsync(String topic, List permissions); + + /** + * Returns the IAM access control policy for the specified subscription. Returns {@code null} if + * the subscription was not found. + * + *

    Example of getting a subscription policy. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Policy policy = pubsub.getSubscriptionPolicy(subscriptionName);
    +   * if (policy == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Policy getSubscriptionPolicy(String subscription); + + /** + * Sends a request for getting the IAM access control policy for the specified subscription. This + * method returns a {@code Future} object to consume the result. {@link Future#get()} returns the + * requested policy or {@code null} if the subscription was not found. + * + *

    Example of asynchronously getting a subscription policy. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Future future = pubsub.getSubscriptionPolicyAsync(subscriptionName);
    +   * // ...
    +   * Policy policy = future.get();
    +   * if (policy == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future getSubscriptionPolicyAsync(String subscription); + + /** + * Sets the IAM access control policy for the specified subscription. Replaces any existing + * policy. This method returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, a + * {@code PubSubException} is thrown, denoting that the server aborted update. If an etag is not + * provided, the policy is overwritten blindly. + * + *

    Example of replacing a subscription policy. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Policy policy = pubsub.getSubscriptionPolicy(subscriptionName);
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * updatedPolicy = pubsub.replaceSubscriptionPolicy(subscriptionName, updatedPolicy);
    +   * }
    + * + * @throws PubSubException upon failure + */ + Policy replaceSubscriptionPolicy(String subscription, Policy newPolicy); + + /** + * Sends a request to set the IAM access control policy for the specified subscription. Replaces + * any existing policy. This method returns a {@code Future} object to consume the result. + * {@link Future#get()} returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, + * {@link Future#get()} will throw a {@link java.util.concurrent.ExecutionException} caused by a + * {@code PubSubException}, denoting that the server aborted update. If an etag is not provided, + * the policy is overwritten blindly. + * + *

    Example of asynchronously replacing a subscription policy. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * Policy policy = pubsub.getSubscriptionPolicy(subscriptionName);
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * Future future =
    +   *     pubsub.replaceSubscriptionPolicyAsync(subscriptionName, updatedPolicy);
    +   * // ...
    +   * updatedPolicy = future.get();
    +   * }
    + * + * @throws PubSubException upon failure + */ + Future replaceSubscriptionPolicyAsync(String subscription, Policy newPolicy); + + /** + * Returns the permissions that a caller has on the specified subscription. You typically don't + * call this method if you're using Google Cloud Platform directly to manage permissions. This + * method is intended for integration with your proprietary software, 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 a subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.subscriptions.get");
    +   * List testedPermissions =
    +   *     pubsub.testSubscriptionPermissions(subscriptionName, 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 + * @see + * Permissions and Roles + */ + List testSubscriptionPermissions(String subscription, List permissions); + + /** + * Sends a request to get the permissions that a caller has on the specified subscription. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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 a + * subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.subscriptions.get");
    +   * Future> future =
    +   *     pubsub.testSubscriptionPermissionsAsync(subscriptionName, 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) + * @throws PubSubException upon failure + * @see + * Permissions and Roles + */ + Future> testSubscriptionPermissionsAsync(String subscription, + List permissions); +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubException.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubException.java new file mode 100644 index 000000000000..fa18d85e2eeb --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubException.java @@ -0,0 +1,46 @@ +/* + * 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.pubsub; + +import com.google.api.gax.grpc.ApiException; +import com.google.cloud.BaseServiceException; + +import java.io.IOException; +import java.util.Set; + +/** + * Pub/Sub service exception. + * + * @see Google Cloud Pub/Sub error codes + */ +public final class PubSubException extends BaseServiceException { + + private static final long serialVersionUID = 6434989638600001226L; + + public PubSubException(IOException ex, boolean idempotent) { + super(ex, idempotent); + } + + public PubSubException(ApiException apiException, boolean idempotent) { + super(apiException, idempotent); + } + + @Override + protected Set getRetryableErrors() { + return null; + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubFactory.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubFactory.java new file mode 100644 index 000000000000..38f922dbce3b --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubFactory.java @@ -0,0 +1,24 @@ +/* + * 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.pubsub; + +import com.google.cloud.ServiceFactory; + +/** + * An interface for Pub/Sub factories. + */ +public interface PubSubFactory extends ServiceFactory {} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubImpl.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubImpl.java new file mode 100644 index 000000000000..e67aee158979 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubImpl.java @@ -0,0 +1,764 @@ +/* + * 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.pubsub; + +import static com.google.cloud.pubsub.PubSub.ListOption.OptionType.PAGE_SIZE; +import static com.google.cloud.pubsub.PubSub.ListOption.OptionType.PAGE_TOKEN; +import static com.google.cloud.pubsub.PubSub.PullOption.OptionType.EXECUTOR_FACTORY; +import static com.google.cloud.pubsub.PubSub.PullOption.OptionType.MAX_QUEUED_CALLBACKS; +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.cloud.AsyncPage; +import com.google.cloud.AsyncPageImpl; +import com.google.cloud.BaseService; +import com.google.cloud.Page; +import com.google.cloud.PageImpl; +import com.google.cloud.Policy; +import com.google.cloud.pubsub.spi.PubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpc.PullFuture; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.Uninterruptibles; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; +import com.google.protobuf.Empty; +import com.google.pubsub.v1.AcknowledgeRequest; +import com.google.pubsub.v1.DeleteSubscriptionRequest; +import com.google.pubsub.v1.DeleteTopicRequest; +import com.google.pubsub.v1.GetSubscriptionRequest; +import com.google.pubsub.v1.GetTopicRequest; +import com.google.pubsub.v1.ListSubscriptionsRequest; +import com.google.pubsub.v1.ListSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicSubscriptionsRequest; +import com.google.pubsub.v1.ListTopicSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicsRequest; +import com.google.pubsub.v1.ListTopicsResponse; +import com.google.pubsub.v1.ModifyAckDeadlineRequest; +import com.google.pubsub.v1.ModifyPushConfigRequest; +import com.google.pubsub.v1.ProjectName; +import com.google.pubsub.v1.PublishRequest; +import com.google.pubsub.v1.PublishResponse; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; +import com.google.pubsub.v1.SubscriptionName; +import com.google.pubsub.v1.TopicName; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +class PubSubImpl extends BaseService implements PubSub { + + private final PubSubRpc rpc; + private final AckDeadlineRenewer ackDeadlineRenewer; + private boolean closed; + + private static final Function EMPTY_TO_VOID_FUNCTION = new Function() { + @Override + public Void apply(Empty empty) { + return null; + } + }; + private static final Function EMPTY_TO_BOOLEAN_FUNCTION = + new Function() { + @Override + public Boolean apply(Empty input) { + return input != null; + } + }; + private static final Function + MESSAGE_TO_ACK_ID_FUNCTION = new Function() { + @Override + public String apply(com.google.pubsub.v1.ReceivedMessage message) { + return message.getAckId(); + } + }; + private static final Function POLICY_TO_PB_FUNCTION = + new Function() { + @Override + public Policy apply(com.google.iam.v1.Policy policyPb) { + return policyPb == null ? null : PolicyMarshaller.INSTANCE.fromPb(policyPb); + } + }; + + PubSubImpl(PubSubOptions options) { + super(options); + rpc = options.getRpc(); + ackDeadlineRenewer = new AckDeadlineRenewer(this); + } + + @VisibleForTesting + PubSubImpl(PubSubOptions options, AckDeadlineRenewer ackDeadlineRenewer) { + super(options); + rpc = options.getRpc(); + this.ackDeadlineRenewer = ackDeadlineRenewer; + } + + private abstract static class BasePageFetcher implements AsyncPageImpl.NextPageFetcher { + + private static final long serialVersionUID = -2122989557125999209L; + + private final PubSubOptions serviceOptions; + private final Map requestOptions; + + private BasePageFetcher(PubSubOptions serviceOptions, String cursor, + Map requestOptions) { + this.serviceOptions = serviceOptions; + this.requestOptions = + PageImpl.nextRequestOptions(PAGE_TOKEN, cursor, requestOptions); + } + + PubSubOptions serviceOptions() { + return serviceOptions; + } + + Map requestOptions() { + return requestOptions; + } + } + + private static class TopicPageFetcher extends BasePageFetcher { + + private static final long serialVersionUID = -7153536453427361814L; + + TopicPageFetcher(PubSubOptions serviceOptions, String cursor, + Map requestOptions) { + super(serviceOptions, cursor, requestOptions); + } + + @Override + @Deprecated + public Future> nextPage() { + return getNextPage(); + } + + @Override + public Future> getNextPage() { + return listTopicsAsync(serviceOptions(), requestOptions()); + } + } + + private static class SubscriptionPageFetcher extends BasePageFetcher { + + private static final long serialVersionUID = -5634446170301177992L; + + SubscriptionPageFetcher(PubSubOptions serviceOptions, String cursor, + Map requestOptions) { + super(serviceOptions, cursor, requestOptions); + } + + @Override + @Deprecated + public Future> nextPage() { + return getNextPage(); + } + + @Override + public Future> getNextPage() { + return listSubscriptionsAsync(serviceOptions(), requestOptions()); + } + } + + private static class SubscriptionNamePageFetcher extends BasePageFetcher { + + private static final long serialVersionUID = 7250525437694464444L; + + private final String topic; + + SubscriptionNamePageFetcher(String topic, PubSubOptions serviceOptions, String cursor, + Map requestOptions) { + super(serviceOptions, cursor, requestOptions); + this.topic = topic; + } + + @Override + @Deprecated + public Future> nextPage() { + return getNextPage(); + } + + @Override + public Future> getNextPage() { + return listSubscriptionsAsync(topic, serviceOptions(), requestOptions()); + } + } + + private static V get(Future future) { + try { + return Uninterruptibles.getUninterruptibly(future); + } catch (ExecutionException ex) { + throw Throwables.propagate(ex.getCause()); + } + } + + private static Future transform(Future future, + Function function) { + return Futures.lazyTransform(future, function); + } + + @Override + public Topic create(TopicInfo topic) { + return get(createAsync(topic)); + } + + @Override + public Future createAsync(TopicInfo topic) { + return transform(rpc.create(topic.toPb(getOptions().getProjectId())), + Topic.fromPbFunction(this)); + } + + @Override + public Topic getTopic(String topic) { + return get(getTopicAsync(topic)); + } + + @Override + public Future getTopicAsync(String topic) { + GetTopicRequest request = GetTopicRequest.newBuilder() + .setTopicWithTopicName(TopicName.create(getOptions().getProjectId(), topic)) + .build(); + return transform(rpc.get(request), Topic.fromPbFunction(this)); + } + + @Override + public boolean deleteTopic(String topic) { + return get(deleteTopicAsync(topic)); + } + + @Override + public Future deleteTopicAsync(String topic) { + DeleteTopicRequest request = DeleteTopicRequest.newBuilder() + .setTopicWithTopicName(TopicName.create(getOptions().getProjectId(), topic)) + .build(); + return transform(rpc.delete(request), EMPTY_TO_BOOLEAN_FUNCTION); + } + + private static ListTopicsRequest listTopicsRequest(PubSubOptions serviceOptions, + Map options) { + ListTopicsRequest.Builder builder = ListTopicsRequest.newBuilder(); + builder.setProjectWithProjectName(ProjectName.create(serviceOptions.getProjectId())); + Integer pageSize = PAGE_SIZE.get(options); + String pageToken = PAGE_TOKEN.get(options); + if (pageSize != null) { + builder.setPageSize(pageSize); + } + if (pageToken != null) { + builder.setPageToken(pageToken); + } + return builder.build(); + } + + private static Future> listTopicsAsync(final PubSubOptions serviceOptions, + final Map options) { + final ListTopicsRequest request = listTopicsRequest(serviceOptions, options); + Future list = serviceOptions.getRpc().list(request); + return transform(list, new Function>() { + @Override + public AsyncPage apply(ListTopicsResponse listTopicsResponse) { + List topics = listTopicsResponse.getTopicsList() == null ? ImmutableList.of() + : Lists.transform(listTopicsResponse.getTopicsList(), + Topic.fromPbFunction(serviceOptions.getService())); + String cursor = listTopicsResponse.getNextPageToken().equals("") ? null + : listTopicsResponse.getNextPageToken(); + return new AsyncPageImpl<>( + new TopicPageFetcher(serviceOptions, cursor, options), cursor, topics); + } + }); + } + + @Override + public Page listTopics(ListOption... options) { + return get(listTopicsAsync(options)); + } + + @Override + public Future> listTopicsAsync(ListOption... options) { + return listTopicsAsync(getOptions(), optionMap(options)); + } + + @Override + public String publish(String topic, Message message) { + return get(publishAsync(topic, message)); + } + + private static PublishRequest publishRequest(PubSubOptions serviceOptions, String topic, + Iterable messages) { + PublishRequest.Builder builder = PublishRequest.newBuilder(); + builder.setTopicWithTopicName(TopicName.create(serviceOptions.getProjectId(), topic)); + builder.addAllMessages(Iterables.transform(messages, Message.TO_PB_FUNCTION)); + return builder.build(); + } + + @Override + public Future publishAsync(String topic, Message message) { + return transform( + rpc.publish(publishRequest(getOptions(), topic, Collections.singletonList(message))), + new Function() { + @Override + public String apply(PublishResponse publishResponse) { + return publishResponse.getMessageIdsList().get(0); + } + }); + } + + @Override + public List publish(String topic, Message message, Message... messages) { + return publish(topic, Lists.asList(message, messages)); + } + + @Override + public Future> publishAsync(String topic, Message message, Message... messages) { + return publishAsync(topic, Lists.asList(message, messages)); + } + + @Override + public List publish(String topic, Iterable messages) { + return get(publishAsync(topic, messages)); + } + + @Override + public Future> publishAsync(String topic, Iterable messages) { + return transform(rpc.publish(publishRequest(getOptions(), topic, messages)), + new Function>() { + @Override + public List apply(PublishResponse publishResponse) { + return publishResponse.getMessageIdsList(); + } + }); + } + + @Override + public Subscription create(SubscriptionInfo subscription) { + return get(createAsync(subscription)); + } + + @Override + public Future createAsync(SubscriptionInfo subscription) { + return transform(rpc.create(subscription.toPb(getOptions().getProjectId())), + Subscription.fromPbFunction(this)); + } + + @Override + public Subscription getSubscription(String subscription) { + return get(getSubscriptionAsync(subscription)); + } + + @Override + public Future getSubscriptionAsync(String subscription) { + GetSubscriptionRequest request = GetSubscriptionRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .build(); + return transform(rpc.get(request), Subscription.fromPbFunction(this)); + } + + @Override + public void replacePushConfig(String subscription, PushConfig pushConfig) { + get(replacePushConfigAsync(subscription, pushConfig)); + } + + @Override + public Future replacePushConfigAsync(String subscription, PushConfig pushConfig) { + ModifyPushConfigRequest request = ModifyPushConfigRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .setPushConfig(pushConfig != null ? pushConfig.toPb() + : com.google.pubsub.v1.PushConfig.getDefaultInstance()) + .build(); + return transform(rpc.modify(request), EMPTY_TO_VOID_FUNCTION); + } + + @Override + public boolean deleteSubscription(String subscription) { + return get(deleteSubscriptionAsync(subscription)); + } + + @Override + public Future deleteSubscriptionAsync(String subscription) { + DeleteSubscriptionRequest request = DeleteSubscriptionRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .build(); + return transform(rpc.delete(request), EMPTY_TO_BOOLEAN_FUNCTION); + } + + private static ListSubscriptionsRequest listSubscriptionsRequest(PubSubOptions serviceOptions, + Map options) { + ListSubscriptionsRequest.Builder builder = ListSubscriptionsRequest.newBuilder(); + builder.setProjectWithProjectName(ProjectName.create(serviceOptions.getProjectId())); + Integer pageSize = PAGE_SIZE.getInteger(options); + String pageToken = PAGE_TOKEN.getString(options); + if (pageSize != null) { + builder.setPageSize(pageSize); + } + if (pageToken != null) { + builder.setPageToken(pageToken); + } + return builder.build(); + } + + private static Future> listSubscriptionsAsync( + final PubSubOptions serviceOptions, final Map options) { + final ListSubscriptionsRequest request = listSubscriptionsRequest(serviceOptions, options); + Future list = serviceOptions.getRpc().list(request); + return transform(list, new Function>() { + @Override + public AsyncPage apply(ListSubscriptionsResponse listSubscriptionsResponse) { + List subscriptions = listSubscriptionsResponse.getSubscriptionsList() == null + ? ImmutableList.of() + : Lists.transform(listSubscriptionsResponse.getSubscriptionsList(), + Subscription.fromPbFunction(serviceOptions.getService())); + String cursor = listSubscriptionsResponse.getNextPageToken().equals("") ? null + : listSubscriptionsResponse.getNextPageToken(); + return new AsyncPageImpl<>(new SubscriptionPageFetcher(serviceOptions, cursor, options), + cursor, subscriptions); + } + }); + } + + @Override + public Page listSubscriptions(ListOption... options) { + return get(listSubscriptionsAsync(options)); + } + + public Future> listSubscriptionsAsync(ListOption... options) { + return listSubscriptionsAsync(getOptions(), optionMap(options)); + } + + private static ListTopicSubscriptionsRequest listSubscriptionsRequest(String topic, + PubSubOptions serviceOptions, Map options) { + ListTopicSubscriptionsRequest.Builder builder = ListTopicSubscriptionsRequest.newBuilder(); + builder.setTopicWithTopicName(TopicName.create(serviceOptions.getProjectId(), topic)); + Integer pageSize = PAGE_SIZE.getInteger(options); + String pageToken = PAGE_TOKEN.getString(options); + if (pageSize != null) { + builder.setPageSize(pageSize); + } + if (pageToken != null) { + builder.setPageToken(pageToken); + } + return builder.build(); + } + + private static Future> listSubscriptionsAsync(final String topic, + final PubSubOptions serviceOptions, final Map options) { + final ListTopicSubscriptionsRequest request = + listSubscriptionsRequest(topic, serviceOptions, options); + Future list = serviceOptions.getRpc().list(request); + return transform(list, + new Function>() { + @Override + public AsyncPage apply( + ListTopicSubscriptionsResponse listSubscriptionsResponse) { + List subscriptions = + listSubscriptionsResponse.getSubscriptionsList() == null + ? ImmutableList.of() + : Lists.transform(listSubscriptionsResponse.getSubscriptionsList(), + new Function() { + @Override + public SubscriptionId apply(String compositeSubscription) { + return SubscriptionId.fromPb(compositeSubscription); + } + }); + String cursor = listSubscriptionsResponse.getNextPageToken().equals("") ? null + : listSubscriptionsResponse.getNextPageToken(); + return new AsyncPageImpl<>( + new SubscriptionNamePageFetcher(topic, serviceOptions, cursor, options), cursor, + subscriptions); + } + }); + } + + @Override + public Page listSubscriptions(String topic, ListOption... options) { + return get(listSubscriptionsAsync(topic, options)); + } + + @Override + public Future> listSubscriptionsAsync(String topic, + ListOption... options) { + return listSubscriptionsAsync(topic, getOptions(), optionMap(options)); + } + + private Future> pullAsync(final String subscription, + int maxMessages, boolean returnImmediately) { + PullRequest request = PullRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .setMaxMessages(maxMessages) + .setReturnImmediately(returnImmediately) + .build(); + PullFuture future = rpc.pull(request); + future.addCallback(new PubSubRpc.PullCallback() { + @Override + public void success(PullResponse response) { + List ackIds = Lists.transform(response.getReceivedMessagesList(), + MESSAGE_TO_ACK_ID_FUNCTION); + ackDeadlineRenewer.add(subscription, ackIds); + } + + @Override + public void failure(Throwable error) { + // ignore + } + }); + return transform(future, new Function>() { + @Override + public Iterator apply(PullResponse response) { + return Iterators.transform(response.getReceivedMessagesList().iterator(), + new Function() { + @Override + public ReceivedMessage apply(com.google.pubsub.v1.ReceivedMessage receivedMessage) { + // Remove consumed message from automatic ack deadline renewer + ackDeadlineRenewer.remove(subscription, receivedMessage.getAckId()); + return ReceivedMessage.fromPb(PubSubImpl.this, subscription, receivedMessage); + } + }); + } + }); + } + + @Override + public Iterator pull(String subscription, int maxMessages) { + return get(pullAsync(subscription, maxMessages, true)); + } + + @Override + public Future> pullAsync(String subscription, int maxMessages) { + return pullAsync(subscription, maxMessages, false); + } + + @Override + public MessageConsumer pullAsync(String subscription, MessageProcessor callback, + PullOption... options) { + Map optionMap = optionMap(options); + return MessageConsumerImpl.builder(getOptions(), subscription, ackDeadlineRenewer, callback) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS.getInteger(optionMap)) + .executorFactory(EXECUTOR_FACTORY.getExecutorFactory(optionMap)) + .build(); + } + + @Override + public void ack(String subscription, String ackId, String... ackIds) { + ack(subscription, Lists.asList(ackId, ackIds)); + } + + @Override + public Future ackAsync(String subscription, String ackId, String... ackIds) { + return ackAsync(subscription, Lists.asList(ackId, ackIds)); + } + + @Override + public void ack(String subscription, Iterable ackIds) { + get(ackAsync(subscription, ackIds)); + } + + @Override + public Future ackAsync(String subscription, Iterable ackIds) { + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .addAllAckIds(ackIds) + .build(); + return transform(rpc.acknowledge(request), EMPTY_TO_VOID_FUNCTION); + } + + @Override + public void nack(String subscription, String ackId, String... ackIds) { + nack(subscription, Lists.asList(ackId, ackIds)); + } + + @Override + public Future nackAsync(String subscription, String ackId, String... ackIds) { + return nackAsync(subscription, Lists.asList(ackId, ackIds)); + } + + @Override + public void nack(String subscription, Iterable ackIds) { + get(nackAsync(subscription, ackIds)); + } + + @Override + public Future nackAsync(String subscription, Iterable ackIds) { + return modifyAckDeadlineAsync(subscription, 0, TimeUnit.SECONDS, ackIds); + } + + @Override + public void modifyAckDeadline(String subscription, int deadline, TimeUnit unit, String ackId, + String... ackIds) { + get(modifyAckDeadlineAsync(subscription, deadline, unit, Lists.asList(ackId, ackIds))); + } + + @Override + public Future modifyAckDeadlineAsync(String subscription, int deadline, TimeUnit unit, + String ackId, String... ackIds) { + return modifyAckDeadlineAsync(subscription, deadline, unit, Lists.asList(ackId, ackIds)); + } + + @Override + public void modifyAckDeadline(String subscription, int deadline, TimeUnit unit, + Iterable ackIds) { + get(modifyAckDeadlineAsync(subscription, deadline, unit, ackIds)); + } + + @Override + public Future modifyAckDeadlineAsync(String subscription, int deadline, TimeUnit unit, + Iterable ackIds) { + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setSubscriptionWithSubscriptionName( + SubscriptionName.create(getOptions().getProjectId(), subscription)) + .setAckDeadlineSeconds((int) TimeUnit.SECONDS.convert(deadline, unit)) + .addAllAckIds(ackIds) + .build(); + return transform(rpc.modify(request), EMPTY_TO_VOID_FUNCTION); + } + + @Override + public Policy getTopicPolicy(String topic) { + return get(getTopicPolicyAsync(topic)); + } + + @Override + public Future getTopicPolicyAsync(String topic) { + return transform( + rpc.getIamPolicy(TopicName.create(getOptions().getProjectId(), topic).toString()), + POLICY_TO_PB_FUNCTION); + } + + @Override + public Policy replaceTopicPolicy(String topic, Policy newPolicy) { + return get(replaceTopicPolicyAsync(topic, newPolicy)); + } + + @Override + public Future replaceTopicPolicyAsync(String topic, Policy newPolicy) { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setPolicy(PolicyMarshaller.INSTANCE.toPb(newPolicy)) + .setResource(TopicName.create(getOptions().getProjectId(), topic).toString()) + .build(); + return transform(rpc.setIamPolicy(request), POLICY_TO_PB_FUNCTION); + } + + @Override + public List testTopicPermissions(String topic, final List permissions) { + return get(testTopicPermissionsAsync(topic, permissions)); + } + + @Override + public Future> testTopicPermissionsAsync(String topic, List permissions) { + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(TopicName.create(getOptions().getProjectId(), topic).toString()) + .addAllPermissions(permissions) + .build(); + return transform(rpc.testIamPermissions(request), permissionsFromPbFunction(permissions)); + } + + @Override + public Policy getSubscriptionPolicy(String subscription) { + return get(getSubscriptionPolicyAsync(subscription)); + } + + @Override + public Future getSubscriptionPolicyAsync(String subscription) { + return transform( + rpc.getIamPolicy( + SubscriptionName.create(getOptions().getProjectId(), subscription).toString()), + POLICY_TO_PB_FUNCTION); + } + + @Override + public Policy replaceSubscriptionPolicy(String subscription, Policy newPolicy) { + return get(replaceSubscriptionPolicyAsync(subscription, newPolicy)); + } + + @Override + public Future replaceSubscriptionPolicyAsync(String subscription, Policy newPolicy) { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setPolicy(PolicyMarshaller.INSTANCE.toPb(newPolicy)) + .setResource( + SubscriptionName.create(getOptions().getProjectId(), subscription).toString()) + .build(); + return transform(rpc.setIamPolicy(request), POLICY_TO_PB_FUNCTION); + } + + @Override + public List testSubscriptionPermissions(String subscription, List permissions) { + return get(testSubscriptionPermissionsAsync(subscription, permissions)); + } + + @Override + public Future> testSubscriptionPermissionsAsync(String subscription, + List permissions) { + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource( + SubscriptionName.create(getOptions().getProjectId(), subscription).toString()) + .addAllPermissions(permissions) + .build(); + return transform(rpc.testIamPermissions(request), permissionsFromPbFunction(permissions)); + } + + private static Function> permissionsFromPbFunction( + final List permissions) { + return new Function>() { + @Override + public List apply(TestIamPermissionsResponse response) { + Set permissionsOwned = ImmutableSet.copyOf( + firstNonNull(response.getPermissionsList(), ImmutableList.of())); + ImmutableList.Builder answer = ImmutableList.builder(); + for (String permission : permissions) { + answer.add(permissionsOwned.contains(permission)); + } + return answer.build(); + } + }; + } + + static Map optionMap(Option... options) { + Map optionMap = Maps.newHashMap(); + for (Option option : options) { + Object prev = optionMap.put(option.getOptionType(), option.getValue()); + checkArgument(prev == null, "Duplicate option %s", option); + } + return optionMap; + } + + @Override + public void close() throws Exception { + if (closed) { + return; + } + closed = true; + rpc.close(); + if (ackDeadlineRenewer != null) { + ackDeadlineRenewer.close(); + } + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubOptions.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubOptions.java new file mode 100644 index 000000000000..ac8262d1af97 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PubSubOptions.java @@ -0,0 +1,144 @@ +/* + * 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.pubsub; + +import com.google.cloud.GrpcServiceOptions; +import com.google.cloud.pubsub.spi.DefaultPubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpcFactory; +import com.google.cloud.pubsub.spi.v1.PublisherSettings; +import com.google.common.collect.ImmutableSet; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; + +public class PubSubOptions extends GrpcServiceOptions { + + private static final long serialVersionUID = 5598666986447361352L; + private static final String PUBSUB_SCOPE = "https://www.googleapis.com/auth/pubsub"; + private static final Set SCOPES = ImmutableSet.of(PUBSUB_SCOPE); + private static final String EMULATOR_HOST_ENV_VAR = "PUBSUB_EMULATOR_HOST"; + private static final String DEFAULT_HOST = PublisherSettings.getDefaultServiceAddress() + + ':' + PublisherSettings.getDefaultServicePort(); + + public static class DefaultPubSubFactory implements PubSubFactory { + private static final PubSubFactory INSTANCE = new DefaultPubSubFactory(); + + @Override + public PubSub create(PubSubOptions options) { + return new PubSubImpl(options); + } + } + + /** + * Returns a default {@code PubSubOptions} instance. + */ + @Deprecated + public static PubSubOptions defaultInstance() { + return getDefaultInstance(); + } + + /** + * Returns a default {@code PubSubOptions} instance. + */ + public static PubSubOptions getDefaultInstance() { + return newBuilder().build(); + } + + public static class DefaultPubSubRpcFactory implements PubSubRpcFactory { + private static final PubSubRpcFactory INSTANCE = new DefaultPubSubRpcFactory(); + + @Override + public PubSubRpc create(PubSubOptions options) { + try { + return new DefaultPubSubRpc(options); + } catch (IOException e) { + throw new PubSubException(e, true); + } + } + } + + @Override + protected String getDefaultHost() { + String host = System.getProperty(EMULATOR_HOST_ENV_VAR, System.getenv(EMULATOR_HOST_ENV_VAR)); + return host != null ? host : DEFAULT_HOST; + } + + public static class Builder extends + GrpcServiceOptions.Builder { + + private Builder() {} + + private Builder(PubSubOptions options) { + super(options); + } + + @Override + public PubSubOptions build() { + return new PubSubOptions(this); + } + } + + protected PubSubOptions(Builder builder) { + super(PubSubFactory.class, PubSubRpcFactory.class, builder); + } + + @Override + protected ExecutorFactory getExecutorFactory() { + return super.getExecutorFactory(); + } + + @Override + protected PubSubFactory getDefaultServiceFactory() { + return DefaultPubSubFactory.INSTANCE; + } + + @Override + protected PubSubRpcFactory getDefaultRpcFactory() { + return DefaultPubSubRpcFactory.INSTANCE; + } + + @Override + protected Set getScopes() { + return SCOPES; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PubSubOptions && baseEquals((PubSubOptions) obj); + } + + @Override + public int hashCode() { + return baseHashCode(); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Deprecated + public static Builder builder() { + return newBuilder(); + } + + public static Builder newBuilder() { + return new Builder(); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PushConfig.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PushConfig.java new file mode 100644 index 000000000000..cd5c4aa06cda --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/PushConfig.java @@ -0,0 +1,363 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Google Cloud Pub/Sub configuration for a push subscription. + * + *

    In a push subscription, the Pub/Sub server sends a request to the subscriber application. A + * {@code PushConfig} object can be used to configure the application endpoint. The subscriber's + * HTTP response serves as an implicit acknowledgement: a success response indicates that the + * message has been succesfully processed and the Pub/Sub system can delete it from the + * subscription; a non-success response indicates that the Pub/Sub server should resend it + * (implicit "nack"). + * + * @see Subscriber Guide + */ +public final class PushConfig implements Serializable { + + private static final long serialVersionUID = 4408885787064092231L; + + private final String endpoint; + private final ImmutableMap attributes; + + /** + * Builder for {@code PushConfig} objects. + */ + public static final class Builder { + + private String endpoint; + private Map attributes = new HashMap<>(); + + private Builder() { + } + + /** + * Sets the URL locating the endpoint to which messages should be pushed. For example, an + * endpoint might use {@code https://example.com/push}. + */ + @Deprecated + public Builder endpoint(String endpoint) { + return setEndpoint(endpoint); + } + + /** + * Sets the URL locating the endpoint to which messages should be pushed. For example, an + * endpoint might use {@code https://example.com/push}. + */ + public Builder setEndpoint(String endpoint) { + this.endpoint = checkNotNull(endpoint); + return this; + } + + /** + * Adds an API-supported attribute that can be used to control different aspects of the message + * delivery. + * + *

    The currently supported attribute is {@code x-goog-version}, which can be used to change + * the format of the push message. This attribute indicates the version of the data expected by + * the endpoint. The endpoint version is based on the version of the Pub/Sub API. Possible + * values for this attribute are: + *

      + *
    • {@code v1beta1}: uses the push format defined in the v1beta1 Pub/Sub API + *
    • {@code v1} or {@code v1beta2}: uses the push format defined in the v1 Pub/Sub API + *
    + * + *

    If the {@code x-goog-version} attribute is not present when a subscription is created (see + * {@link PubSub#create(SubscriptionInfo)} and {@link PubSub#createAsync(SubscriptionInfo)}), it + * will default to {@code v1}. If it is not present when modifying the push config (see + * {@link PubSub#replacePushConfig(String, PushConfig)} and + * {@link PubSub#replacePushConfigAsync(String, PushConfig)}), its value will not be changed. + * + * @see Message Format + */ + public Builder addAttribute(String name, String value) { + attributes.put(name, value); + return this; + } + + /** + * Sets the API-supported attributes that can be used to control different aspects of the + * message delivery. + * + *

    The currently supported attribute is {@code x-goog-version}, which can be used to change + * the format of the push message. This attribute indicates the version of the data expected by + * the endpoint. The endpoint version is based on the version of the Pub/Sub API. Possible + * values for this attribute are: + *

      + *
    • {@code v1beta1}: uses the push format defined in the v1beta1 Pub/Sub API + *
    • {@code v1} or {@code v1beta2}: uses the push format defined in the v1 Pub/Sub API + *
    + * + *

    If the {@code x-goog-version} attribute is not present when a subscription is created (see + * {@link PubSub#create(SubscriptionInfo)} and {@link PubSub#createAsync(SubscriptionInfo)}), it + * will default to {@code v1}. If it is not present when modifying the push config (see + * {@link PubSub#replacePushConfig(String, PushConfig)} and + * {@link PubSub#replacePushConfigAsync(String, PushConfig)}), its value will not be changed. + * + * @see Message Format + */ + @Deprecated + public Builder attributes(Map attributes) { + return setAttributes(attributes); + } + + /** + * Sets the API-supported attributes that can be used to control different aspects of the + * message delivery. + * + *

    The currently supported attribute is {@code x-goog-version}, which can be used to change + * the format of the push message. This attribute indicates the version of the data expected by + * the endpoint. The endpoint version is based on the version of the Pub/Sub API. Possible + * values for this attribute are: + *

      + *
    • {@code v1beta1}: uses the push format defined in the v1beta1 Pub/Sub API + *
    • {@code v1} or {@code v1beta2}: uses the push format defined in the v1 Pub/Sub API + *
    + * + *

    If the {@code x-goog-version} attribute is not present when a subscription is created (see + * {@link PubSub#create(SubscriptionInfo)} and {@link PubSub#createAsync(SubscriptionInfo)}), it + * will default to {@code v1}. If it is not present when modifying the push config (see + * {@link PubSub#replacePushConfig(String, PushConfig)} and + * {@link PubSub#replacePushConfigAsync(String, PushConfig)}), its value will not be changed. + * + * @see Message Format + */ + public Builder setAttributes(Map attributes) { + this.attributes = new HashMap<>(attributes); + return this; + } + + /** + * Removes an API-supported attribute. + */ + public Builder removeAttribute(String name) { + attributes.remove(name); + return this; + } + + /** + * Clears all API-supported attributes. + */ + public Builder clearAttributes() { + attributes.clear(); + return this; + } + + /** + * Creates a {@code PushConfig} object. + */ + public PushConfig build() { + return new PushConfig(this); + } + } + + private PushConfig(Builder builder) { + endpoint = builder.endpoint; + attributes = ImmutableMap.copyOf(builder.attributes); + } + + /** + * Returns the URL locating the endpoint to which messages should be pushed. For example, an + * endpoint might use {@code https://example.com/push}. + */ + @Deprecated + public String endpoint() { + return getEndpoint(); + } + + /** + * Returns the URL locating the endpoint to which messages should be pushed. For example, an + * endpoint might use {@code https://example.com/push}. + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Returns the API-supported attributes that can be used to control different aspects of the + * message delivery. + * + *

    The currently supported attribute is {@code x-goog-version}, which can be used to change + * the format of the push message. This attribute indicates the version of the data expected by + * the endpoint. The endpoint version is based on the version of the Pub/Sub API. Possible + * values for this attribute are: + *

      + *
    • {@code v1beta1}: uses the push format defined in the v1beta1 Pub/Sub API + *
    • {@code v1} or {@code v1beta2}: uses the push format defined in the v1 Pub/Sub API + *
    + * + *

    If the {@code x-goog-version} attribute is not present when a subscription is created (see + * {@link PubSub#create(SubscriptionInfo)} and {@link PubSub#createAsync(SubscriptionInfo)}), it + * will default to {@code v1}. If it is not present when modifying the push config (see + * {@link PubSub#replacePushConfig(String, PushConfig)} and + * {@link PubSub#replacePushConfigAsync(String, PushConfig)}), its value will not be changed. + * + * @see Message Format + */ + @Deprecated + public Map attributes() { + return getAttributes(); + } + + /** + * Returns the API-supported attributes that can be used to control different aspects of the + * message delivery. + * + *

    The currently supported attribute is {@code x-goog-version}, which can be used to change + * the format of the push message. This attribute indicates the version of the data expected by + * the endpoint. The endpoint version is based on the version of the Pub/Sub API. Possible + * values for this attribute are: + *

      + *
    • {@code v1beta1}: uses the push format defined in the v1beta1 Pub/Sub API + *
    • {@code v1} or {@code v1beta2}: uses the push format defined in the v1 Pub/Sub API + *
    + * + *

    If the {@code x-goog-version} attribute is not present when a subscription is created (see + * {@link PubSub#create(SubscriptionInfo)} and {@link PubSub#createAsync(SubscriptionInfo)}), it + * will default to {@code v1}. If it is not present when modifying the push config (see + * {@link PubSub#replacePushConfig(String, PushConfig)} and + * {@link PubSub#replacePushConfigAsync(String, PushConfig)}), its value will not be changed. + * + * @see Message Format + */ + public Map getAttributes() { + return attributes; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PushConfig)) { + return false; + } + PushConfig other = (PushConfig) obj; + return Objects.equals(endpoint, other.endpoint) && Objects.equals(attributes, other.attributes); + } + + @Override + public int hashCode() { + return Objects.hash(endpoint, attributes); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("attributes", attributes) + .add("endpoint", endpoint) + .toString(); + } + + /** + * Returns a builder for the {@code PushConfig} object. + */ + public Builder toBuilder() { + return newBuilder(endpoint, attributes); + } + + /** + * Creates a {@code PushConfig} object given the push endpoint. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + */ + public static PushConfig of(String endpoint) { + return newBuilder(endpoint).build(); + } + + /** + * Creates a {@code PushConfig} object given the push endpoint and the API-supported attributes + * that can be used to control different aspects of the message delivery. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + * @param attributes API supported attributes used to control message delivery. See + * {@link Builder#attributes(Map)} for more details. + */ + public static PushConfig of(String endpoint, Map attributes) { + return newBuilder(endpoint, attributes).build(); + } + + /** + * Creates a builder for {@code PushConfig} objects given the push endpoint. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + */ + @Deprecated + public static Builder builder(String endpoint) { + return newBuilder(endpoint); + } + + /** + * Creates a builder for {@code PushConfig} objects given the push endpoint. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + */ + public static Builder newBuilder(String endpoint) { + return new Builder().setEndpoint(endpoint); + } + + /** + * Creates a builder for {@code PushConfig} objects given the push endpoint and the API-supported + * attributes that can be used to control different aspects of the message delivery. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + * @param attributes API supported attributes used to control message delivery. See + * {@link Builder#attributes(Map)} for more details. + */ + @Deprecated + public static Builder builder(String endpoint, Map attributes) { + return newBuilder(endpoint, attributes); + } + + /** + * Creates a builder for {@code PushConfig} objects given the push endpoint and the API-supported + * attributes that can be used to control different aspects of the message delivery. + * + * @param endpoint the URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + * @param attributes API supported attributes used to control message delivery. See + * {@link Builder#attributes(Map)} for more details. + */ + public static Builder newBuilder(String endpoint, Map attributes) { + return newBuilder(endpoint).setAttributes(attributes); + } + + com.google.pubsub.v1.PushConfig toPb() { + return com.google.pubsub.v1.PushConfig.newBuilder().setPushEndpoint(endpoint) + .putAllAttributes(attributes).build(); + } + + static PushConfig fromPb(com.google.pubsub.v1.PushConfig pushConfigPb) { + return newBuilder(pushConfigPb.getPushEndpoint(), pushConfigPb.getAttributesMap()).build(); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/ReceivedMessage.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/ReceivedMessage.java new file mode 100644 index 000000000000..4afbef2e5a9b --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/ReceivedMessage.java @@ -0,0 +1,297 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.cloud.ByteArray; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * A Google Cloud Pub/Sub received message. A received message has all the information in + * {@link Message} as well as the acknowledge id. The ack id can be used to acknowledge the received + * message. + * + *

    {@code ReceivedMessage} also adds a layer of service-related functionality over + * {@link Message} that help manage received messages (see {@link #ack()}, {@link #nack()} and + * {@link #modifyAckDeadline(int, TimeUnit)}). + */ +public final class ReceivedMessage extends Message { + + private static final long serialVersionUID = -4178477763916251733L; + + private final String subscription; + private final String ackId; + private transient PubSub pubsub; + private final PubSubOptions options; + + public static final class Builder extends Message.Builder { + + private final String subscription; + private final String ackId; + private final PubSub pubsub; + private final BuilderImpl delegate; + + private Builder(String subscription, String ackId, PubSub pubsub, BuilderImpl delegate) { + this.subscription = subscription; + this.ackId = ackId; + this.pubsub = pubsub; + this.delegate = delegate; + } + + @Override + Builder setId(String id) { + delegate.setId(id); + return this; + } + + @Override + @Deprecated + public Builder payload(String payload) { + return setPayload(payload); + } + + @Override + public Builder setPayload(String payload) { + delegate.setPayload(payload); + return this; + } + + @Override + @Deprecated + public Builder payload(ByteArray payload) { + return setPayload(payload); + } + + @Override + public Builder setPayload(ByteArray payload) { + delegate.setPayload(payload); + return this; + } + + @Override + @Deprecated + public Builder attributes(Map attributes) { + return setAttributes(attributes); + } + + @Override + public Builder setAttributes(Map attributes) { + delegate.setAttributes(attributes); + return this; + } + + @Override + public Builder addAttribute(String name, String value) { + delegate.addAttribute(name, value); + return this; + } + + @Override + public Builder removeAttribute(String name) { + delegate.removeAttribute(name); + return this; + } + + @Override + public Builder clearAttributes() { + delegate.clearAttributes(); + return this; + } + + @Override + Builder setPublishTime(long publishTime) { + delegate.setPublishTime(publishTime); + return this; + } + + @Override + public ReceivedMessage build() { + return new ReceivedMessage(this); + } + } + + ReceivedMessage(Builder builder) { + super(builder.delegate); + subscription = checkNotNull(builder.subscription); + ackId = checkNotNull(builder.ackId); + pubsub = checkNotNull(builder.pubsub); + options = pubsub.getOptions(); + } + + @Override + public Builder toBuilder() { + return new Builder(subscription, ackId, pubsub, new BuilderImpl(this)); + } + + @Override + public int hashCode() { + return Objects.hash(options, super.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !obj.getClass().equals(ReceivedMessage.class)) { + return false; + } + ReceivedMessage other = (ReceivedMessage) obj; + return baseEquals(other) && Objects.equals(options, other.options); + } + + /** + * Returns the received message's {@code PubSub} object used to issue requests. + */ + @Deprecated + public PubSub pubsub() { + return getPubsub(); + } + + /** + * Returns the received message's {@code PubSub} object used to issue requests. + */ + public PubSub getPubsub() { + return pubsub; + } + + /** + * Returns the name of the subscription this message was received from. + */ + @Deprecated + public String subscription() { + return getSubscription(); + } + + /** + * Returns the name of the subscription this message was received from. + */ + public String getSubscription() { + return subscription; + } + + /** + * Returns the acknowledge id of the message. The ack id can be used to acknowledge the received + * message. + */ + @Deprecated + public String ackId() { + return getAckId(); + } + + /** + * Returns the acknowledge id of the message. The ack id can be used to acknowledge the received + * message. + */ + public String getAckId() { + return ackId; + } + + /** + * Acknowledges the current message. + * + * @throws PubSubException upon failure, or if the subscription was not found + */ + public void ack() { + pubsub.ack(subscription, ackId); + } + + /** + * Sends a request to acknowledge the current message. The method returns a {@code Future} object + * that can be used to wait for the acknowledge operation to be completed. + * + * @throws PubSubException upon failure, or if the subscription was not found + */ + public Future ackAsync() { + return pubsub.ackAsync(subscription, ackId); + } + + /** + * "Nacks" the current message. This method corresponds to calling + * {@link #modifyAckDeadline(int, TimeUnit)} with a deadline of 0. + * + * @throws PubSubException upon failure, or if the subscription was not found + */ + public void nack() { + pubsub.nack(subscription, ackId); + } + + /** + * Sends a request to "nack" the current message. This method corresponds to calling + * {@link #modifyAckDeadlineAsync(int, TimeUnit)} with a deadline of 0. The method returns a + * {@code Future} object that can be used to wait for the "nack" operation to be completed. + * + * @throws PubSubException upon failure, or if the subscription was not found + */ + public Future nackAsync() { + return pubsub.nackAsync(subscription, ackId); + } + + /** + * Modifies the acknowledge deadline of the current message. {@code deadline} must be >= 0 and + * is the new deadline with respect to the time the modify request was received by the Pub/Sub + * service. For example, if {@code deadline} is 10 and {@code unit} is {@link TimeUnit#SECONDS}, + * the new ack deadline will expire 10 seconds after the modify request was received by the + * service. Specifying 0 may be used to make the message available for another pull request + * (corresponds to calling {@link #nack()}. + * + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit {@code deadline} time unit + * @throws PubSubException upon failure, or if the subscription was not found + */ + public void modifyAckDeadline(int deadline, TimeUnit unit) { + pubsub.modifyAckDeadline(subscription, deadline, unit, ackId); + } + + /** + * Sends a request to modify the acknowledge deadline of the given messages. {@code deadline} + * must be >= 0 and is the new deadline with respect to the time the modify request was + * received by the Pub/Sub service. For example, if {@code deadline} is 10 and {@code unit} is + * {@link TimeUnit#SECONDS}, the new ack deadline will expire 10 seconds after the modify request + * was received by the service. Specifying 0 may be used to make the message available for another + * pull request (corresponds to calling {@link #nackAsync()}. The method returns a {@code Future} + * object that can be used to wait for the modify operation to be completed. + * + * @param deadline the new deadline, relative to the time the modify request is received by the + * Pub/Sub service + * @param unit {@code deadline} time unit + * @throws PubSubException upon failure, or if the subscription was not found + */ + public Future modifyAckDeadlineAsync(int deadline, TimeUnit unit) { + return pubsub.modifyAckDeadlineAsync(subscription, deadline, unit, ackId); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + this.pubsub = options.getService(); + } + + static ReceivedMessage fromPb(PubSub pubsub, String subscription, + com.google.pubsub.v1.ReceivedMessage msgPb) { + Message message = fromPb(msgPb.getMessage()); + String ackId = msgPb.getAckId(); + return new Builder(subscription, ackId, pubsub, new BuilderImpl(message)).build(); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Subscription.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Subscription.java new file mode 100644 index 000000000000..85a270a5fcc0 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Subscription.java @@ -0,0 +1,611 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.cloud.GrpcServiceOptions; +import com.google.cloud.Policy; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PubSub.PullOption; +import com.google.common.base.Function; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Future; + +/** + * A Google Cloud Pub/Sub subscription. A subscription represents the stream of messages from a + * single, specific topic, to be delivered to the subscribing application. Pub/Sub subscriptions + * support both push and pull message delivery. + * + *

    In a push subscription, the Pub/Sub server sends a request to the subscriber application, at a + * preconfigured endpoint (see {@link PushConfig}). The subscriber's HTTP response serves as an + * implicit acknowledgement: a success response indicates that the message has been succesfully + * processed and the Pub/Sub system can delete it from the subscription; a non-success response + * indicates that the Pub/Sub server should resend it (implicit "nack"). + * + *

    In a pull subscription, the subscribing application must explicitly pull messages using one of + * {@link PubSub#pull(String, int)}, {@link PubSub#pullAsync(String, int)} or + * {@link PubSub#pullAsync(String, PubSub.MessageProcessor callback, PubSub.PullOption...)}. + * When messages are pulled with {@link PubSub#pull(String, int)} or + * {@link PubSub#pullAsync(String, int)} the subscribing application must also explicitly + * acknowledge them using one of {@link PubSub#ack(String, Iterable)}, + * {@link PubSub#ack(String, String, String...)}, {@link PubSub#ackAsync(String, Iterable)} or + * {@link PubSub#ackAsync(String, String, String...)}. + * + *

    {@code Subscription} adds a layer of service-related functionality over + * {@link SubscriptionInfo}. Objects of this class are immutable. To get a {@code Subscription} + * object with the most recent information use {@link #reload} or {@link #reloadAsync}. + * + * @see Pub/Sub Data Model + * @see Subscriber Guide + */ +public class Subscription extends SubscriptionInfo { + + private static final long serialVersionUID = -4153366055659552230L; + + private final PubSubOptions options; + private transient PubSub pubsub; + + /** + * A builder for {@code Subscription} objects. + */ + public static final class Builder extends SubscriptionInfo.Builder { + + private final PubSub pubsub; + private final BuilderImpl delegate; + + private Builder(Subscription subscription) { + pubsub = subscription.pubsub; + delegate = new BuilderImpl(subscription); + } + + @Override + @Deprecated + public Builder topic(TopicId topic) { + return setTopic(topic); + } + + @Override + public Builder setTopic(TopicId topic) { + delegate.setTopic(topic); + return this; + } + + @Override + @Deprecated + public Builder topic(String project, String topic) { + return setTopic(project, topic); + } + + @Override + public Builder setTopic(String project, String topic) { + delegate.setTopic(project, topic); + return this; + } + + @Override + @Deprecated + public Builder topic(String topic) { + return setTopic(topic); + } + + @Override + public Builder setTopic(String topic) { + delegate.setTopic(topic); + return this; + } + + @Override + @Deprecated + public Builder name(String name) { + return setName(name); + } + + @Override + public Builder setName(String name) { + delegate.setName(name); + return this; + } + + @Override + @Deprecated + public Builder pushConfig(PushConfig pushConfig) { + return setPushConfig(pushConfig); + } + + @Override + public Builder setPushConfig(PushConfig pushConfig) { + delegate.setPushConfig(pushConfig); + return this; + } + + @Override + @Deprecated + public Builder ackDeadLineSeconds(int ackDeadLineSeconds) { + return setAckDeadLineSeconds(ackDeadLineSeconds); + } + + @Override + public Builder setAckDeadLineSeconds(int ackDeadLineSeconds) { + delegate.setAckDeadLineSeconds(ackDeadLineSeconds); + return this; + } + + @Override + public Subscription build() { + return new Subscription(this.pubsub, this.delegate); + } + } + + Subscription(PubSub pubsub, BuilderImpl builder) { + super(builder); + this.pubsub = checkNotNull(pubsub); + options = pubsub.getOptions(); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public final int hashCode() { + return Objects.hash(options, super.hashCode()); + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || !obj.getClass().equals(Subscription.class)) { + return false; + } + Subscription other = (Subscription) obj; + return baseEquals(other) && Objects.equals(options, other.options); + } + + /** + * Returns the subscription's {@code PubSub} object used to issue requests. + */ + @Deprecated + public PubSub pubSub() { + return getPubsub(); + } + + /** + * Returns the subscription's {@code PubSub} object used to issue requests. + */ + public PubSub getPubsub() { + return pubsub; + } + + /** + * Deletes this subscription. + * + *

    Example of deleting the subscription. + *

     {@code
    +   * boolean deleted = subscription.delete();
    +   * if (deleted) {
    +   *   // the subscription was deleted
    +   * } else {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + * @return {@code true} if the subscription was deleted, {@code false} if it was not found + * @throws PubSubException upon failure + */ + public boolean delete() { + return pubsub.deleteSubscription(getName()); + } + + /** + * Sends a request for deleting this subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns {@code true} if the subscription was deleted, + * {@code false} if it was not found. + * + *

    Example of asynchronously deleting the subscription. + *

     {@code
    +   * Future future = subscription.deleteAsync();
    +   * // ...
    +   * boolean deleted = future.get();
    +   * if (deleted) {
    +   *   // the subscription was deleted
    +   * } else {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + */ + public Future deleteAsync() { + return pubsub.deleteSubscriptionAsync(getName()); + } + + /** + * Fetches current subscription's latest information. Returns {@code null} if the subscription + * does not exist. + * + *

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

     {@code
    +   * Subscription latestSubscription = subscription.reload();
    +   * if (latestSubscription == null) {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + * @return a {@code Subscription} object with latest information or {@code null} if not found + * @throws PubSubException upon failure + */ + public Subscription reload() { + return pubsub.getSubscription(getName()); + } + + /** + * Sends a request for fetching current subscription's latest information. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns the requested + * subscription or {@code null} if not found. + * + *

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

     {@code
    +   * Future future = subscription.reloadAsync();
    +   * // ...
    +   * Subscription latestSubscription = future.get();
    +   * if (latestSubscription == null) {
    +   *   // the subscription was not found
    +   * }
    +   * }
    + * + * @return a {@code Subscription} object with latest information or {@code null} if not found + * @throws PubSubException upon failure + */ + public Future reloadAsync() { + return pubsub.getSubscriptionAsync(getName()); + } + + /** + * Sets the push configuration for this subscription. This may be used to change a push + * subscription to a pull one (passing a {@code null} {@code pushConfig} parameter) or vice versa. + * This methods can also be used to change the endpoint URL and other attributes of a push + * subscription. Messages will accumulate for delivery regardless of changes to the push + * configuration. + * + *

    Example of replacing the push configuration of the subscription, setting the push endpoint. + *

     {@code
    +   * String endpoint = "https://www.example.com/push";
    +   * PushConfig pushConfig = PushConfig.of(endpoint);
    +   * subscription.replacePushConfig(pushConfig);
    +   * }
    + * + *

    Example of replacing the push configuration of the subscription, making it a pull + * subscription. + *

     {@code
    +   * subscription.replacePushConfig(null);
    +   * }
    + * + * @param pushConfig the new push configuration. Use {@code null} to unset it + * @throws PubSubException upon failure, or if the subscription does not exist + */ + public void replacePushConfig(PushConfig pushConfig) { + pubsub.replacePushConfig(getName(), pushConfig); + } + + /** + * Sends a request for updating the push configuration for a specified subscription. This may be + * used to change a push subscription to a pull one (passing a {@code null} {@code pushConfig} + * parameter) or vice versa. This methods can also be used to change the endpoint URL and other + * attributes of a push subscription. Messages will accumulate for delivery regardless of changes + * to the push configuration. The method returns a {@code Future} object that can be used to wait + * for the replace operation to be completed. + * + *

    Example of asynchronously replacing the push configuration of the subscription, setting the + * push endpoint. + *

     {@code
    +   * String endpoint = "https://www.example.com/push";
    +   * PushConfig pushConfig = PushConfig.of(endpoint);
    +   * Future future = subscription.replacePushConfigAsync(pushConfig);
    +   * // ...
    +   * future.get();
    +   * }
    + * + *

    Example of asynchronously replacing the push configuration of the subscription, making it a + * pull subscription. + *

     {@code
    +   * Future future = subscription.replacePushConfigAsync(null);
    +   * // ...
    +   * future.get();
    +   * }
    + * + * @param pushConfig the new push configuration. Use {@code null} to unset it + * @return a {@code Future} to wait for the replace operation to be completed. + */ + public Future replacePushConfigAsync(PushConfig pushConfig) { + return pubsub.replacePushConfigAsync(getName(), pushConfig); + } + + /** + * Pulls messages from this subscription. This method possibly returns no messages if no message + * was available at the time the request was processed by the Pub/Sub service (i.e. the system is + * not allowed to wait until at least one message is available). Pulled messages have their + * acknowledge deadline automatically renewed until they are explicitly consumed using + * {@link Iterator#next()}. + * + *

    Example of pulling a maximum number of messages from the subscription. + *

     {@code
    +   * Iterator messages = subscription.pull(100);
    +   * // Ack deadline is renewed until the message is consumed
    +   * while (messages.hasNext()) {
    +   *   ReceivedMessage message = messages.next();
    +   *   // do something with message and ack/nack it
    +   *   message.ack(); // or message.nack()
    +   * }
    +   * }
    + * + * @param maxMessages the maximum number of messages pulled by this method. This method can + * possibly return fewer messages. + * @throws PubSubException upon failure + */ + public Iterator pull(int maxMessages) { + return pubsub.pull(getName(), maxMessages); + } + + /** + * Sends a request for pulling messages from this subscription. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a message iterator. + * This method possibly returns no messages if no message was available at the time the request + * was processed by the Pub/Sub service (i.e. the system is not allowed to wait until at least one + * message is available). + * + *

    Example of asynchronously pulling a maximum number of messages from the subscription. + *

     {@code
    +   * Future> future = subscription.pullAsync(100);
    +   * // ...
    +   * Iterator messages = future.get();
    +   * // Ack deadline is renewed until the message is consumed
    +   * while (messages.hasNext()) {
    +   *   ReceivedMessage message = messages.next();
    +   *   // do something with message and ack/nack it
    +   *   message.ack(); // or message.nack()
    +   * }
    +   * }
    + * + * @param maxMessages the maximum number of messages pulled by this method. This method can + * possibly return fewer messages. + * @throws PubSubException upon failure + */ + public Future> pullAsync(int maxMessages) { + return pubsub.pullAsync(getName(), maxMessages); + } + + /** + * Creates a message consumer that pulls messages from this subscription. You can stop pulling + * messages by calling {@link MessageConsumer#close()}. The returned message consumer executes + * {@link MessageProcessor#process(Message)} on each pulled message. If + * {@link MessageProcessor#process(Message)} executes correctly, the message is acknowledged. If + * {@link MessageProcessor#process(Message)} throws an exception, the message is "nacked". For + * all pulled messages, the ack deadline is automatically renewed until the message is either + * acknowledged or "nacked". + * + *

    The {@link PullOption#maxQueuedCallbacks(int)} option can be used to control the maximum + * number of queued messages (messages either being processed or waiting to be processed). The + * {@link PullOption#executorFactory(GrpcServiceOptions.ExecutorFactory)} can be used to provide + * an executor to run message processor callbacks. + * + *

    Example of continuously pulling messages from the subscription. + *

     {@code
    +   * String subscriptionName = "my_subscription_name";
    +   * MessageProcessor callback = new MessageProcessor() {
    +   *   public void process(Message message) throws Exception {
    +   *     // Ack deadline is renewed until this method returns
    +   *     // Message is acked if this method returns successfully
    +   *     // Message is nacked if this method throws an exception
    +   *   }
    +   * };
    +   * MessageConsumer consumer = subscription.pullAsync(callback);
    +   * // ...
    +   * // Stop pulling
    +   * consumer.close();
    +   * }
    + * + * @param callback the callback to be executed on each message + * @param options pulling options + * @return a message consumer for the provided subscription and options + */ + public MessageConsumer pullAsync(MessageProcessor callback, PullOption... options) { + return pubsub.pullAsync(getName(), callback, options); + } + + /** + * Returns the IAM access control policy for this subscription. Returns {@code null} if the + * subscription was not found. + * + *

    Example of getting the subscription's policy. + *

     {@code
    +   * Policy policy = subscription.getPolicy();
    +   * if (policy == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + public Policy getPolicy() { + return pubsub.getSubscriptionPolicy(this.getName()); + } + + /** + * Sends a request for getting the IAM access control policy for this subscription. This method + * returns a {@code Future} object to consume the result. {@link Future#get()} returns the + * requested policy or {@code null} if the subscription was not found. + * + *

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

     {@code
    +   * Future future = subscription.getPolicyAsync();
    +   * // ...
    +   * Policy policy = future.get();
    +   * if (policy == null) {
    +   *   // subscription was not found
    +   * }
    +   * }
    + * + * @throws PubSubException upon failure + */ + public Future getPolicyAsync() { + return pubsub.getSubscriptionPolicyAsync(this.getName()); + } + + /** + * Sets the IAM access control policy for this subscription. Replaces any existing policy. This + * method returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, a + * {@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 subscription's policy. + *

     {@code
    +   * Policy policy = subscription.getPolicy();
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * updatedPolicy = subscription.replacePolicy(updatedPolicy);
    +   * }
    + * + * @throws PubSubException upon failure + */ + public Policy replacePolicy(Policy newPolicy) { + return pubsub.replaceSubscriptionPolicy(this.getName(), newPolicy); + } + + /** + * Sends a request to set the IAM access control policy for this subscription. Replaces any + * existing policy. This method returns a {@code Future} object to consume the result. + * {@link Future#get()} returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, + * {@link Future#get()} will throw a {@link java.util.concurrent.ExecutionException} caused by a + * {@code PubSubException}, denoting that the server aborted update. If an etag is not provided, + * the policy is overwritten blindly. + * + *

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

     {@code
    +   * Policy policy = subscription.getPolicy();
    +   * Policy updatedPolicy = policy.toBuilder()
    +   *     .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers())
    +   *     .build();
    +   * Future future = subscription.replacePolicyAsync(updatedPolicy);
    +   * // ...
    +   * updatedPolicy = future.get();
    +   * }
    + * + * @throws PubSubException upon failure + */ + public Future replacePolicyAsync(Policy newPolicy) { + return pubsub.replaceSubscriptionPolicyAsync(this.getName(), newPolicy); + } + + /** + * Returns the permissions that a caller has on this subscription. You typically don't call this + * method if you're using Google Cloud Platform directly to manage permissions. This method is + * intended for integration with your proprietary software, 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 subscription. + *

     {@code
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.subscriptions.get");
    +   * List testedPermissions = subscription.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 + * @see + * Permissions and Roles + */ + public List testPermissions(List permissions) { + return pubsub.testSubscriptionPermissions(this.getName(), permissions); + } + + /** + * Sends a request to get the permissions that a caller has on this subscription. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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 + * subscription. + *

     {@code
    +   * List permissions = new LinkedList<>();
    +   * permissions.add("pubsub.subscriptions.get");
    +   * Future> future = subscription.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) + * @throws PubSubException upon failure + * @see + * Permissions and Roles + */ + public Future> testPermissionsAsync(List permissions) { + return pubsub.testSubscriptionPermissionsAsync(this.getName(), permissions); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + this.pubsub = options.getService(); + } + + static Subscription fromPb(PubSub storage, com.google.pubsub.v1.Subscription subscriptionPb) { + SubscriptionInfo subscriptionInfo = SubscriptionInfo.fromPb(subscriptionPb); + return new Subscription(storage, new BuilderImpl(subscriptionInfo)); + } + + static Function fromPbFunction( + final PubSub pubsub) { + return new Function() { + @Override + public Subscription apply(com.google.pubsub.v1.Subscription subscriptionPb) { + return subscriptionPb != null ? fromPb(pubsub, subscriptionPb) : null; + } + }; + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionId.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionId.java new file mode 100644 index 000000000000..a01fc9dfc79e --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionId.java @@ -0,0 +1,104 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.pubsub.v1.SubscriptionName; +import java.io.Serializable; +import java.util.Objects; + +/** + * Identity for a Google PubSub subscription. {@code SubscriptionId} objects are returned by the + * {@link PubSub#listSubscriptions(String, PubSub.ListOption...)} and + * {@link PubSub#listSubscriptionsAsync(String, PubSub.ListOption...)} methods as a topic may have + * subscriptions from different projects. + */ +public class SubscriptionId implements Serializable { + + private static final long serialVersionUID = 6507142968866856283L; + + private final String project; + private final String subscription; + + SubscriptionId(String project, String subscription) { + this.project = checkNotNull(project); + this.subscription = checkNotNull(subscription); + } + + /** + * Returns the name of the project where the subscription resides. + */ + @Deprecated + public String project() { + return getProject(); + } + + /** + * Returns the name of the project where the subscription resides. + */ + public String getProject() { + return project; + } + + /** + * Returns the name of the subscription. + */ + @Deprecated + public String subscription() { + return getSubscription(); + } + + /** + * Returns the name of the subscription. + */ + public String getSubscription() { + return subscription; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("project", project) + .add("subscription", subscription).toString(); + } + + @Override + public final int hashCode() { + return Objects.hash(project, subscription); + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof SubscriptionId)) { + return false; + } + SubscriptionId other = (SubscriptionId) obj; + return Objects.equals(project, other.project) + && Objects.equals(subscription, other.subscription); + } + + static SubscriptionId fromPb(String pb) { + SubscriptionName subscriptionName = SubscriptionName.parse(pb); + return new SubscriptionId(subscriptionName.getProject(), + subscriptionName.getSubscription()); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionInfo.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionInfo.java new file mode 100644 index 000000000000..b5ff3106dbd0 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/SubscriptionInfo.java @@ -0,0 +1,556 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.pubsub.v1.SubscriptionName; +import java.io.Serializable; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * A Google Cloud Pub/Sub subscription. A subscription represents the stream of messages from a + * single, specific topic, to be delivered to the subscribing application. Pub/Sub subscriptions + * support both push and pull message delivery. + * + *

    In a push subscription, the Pub/Sub server sends a request to the subscriber application, at a + * preconfigured endpoint (see {@link PushConfig}). The subscriber's HTTP response serves as an + * implicit acknowledgement: a success response indicates that the message has been succesfully + * processed and the Pub/Sub system can delete it from the subscription; a non-success response + * indicates that the Pub/Sub server should resend it (implicit "nack"). + * + *

    In a pull subscription, the subscribing application must explicitly pull messages using one of + * {@link PubSub#pull(String, int)}, {@link PubSub#pullAsync(String, int)} or + * {@link PubSub#pullAsync(String, PubSub.MessageProcessor callback, PubSub.PullOption...)}. + * When messages are pulled with {@link PubSub#pull(String, int)} or + * {@link PubSub#pullAsync(String, int)} the subscribing application must also explicitly + * acknowledge them using one of {@link PubSub#ack(String, Iterable)}, + * {@link PubSub#ack(String, String, String...)}, {@link PubSub#ackAsync(String, Iterable)} or + * {@link PubSub#ackAsync(String, String, String...)}. + * + * @see Pub/Sub Data Model + * @see Subscriber Guide + */ +public class SubscriptionInfo implements Serializable { + + private static final long serialVersionUID = 1860057426574127128L; + + private final String name; + private final TopicId topic; + private final PushConfig pushConfig; + private final int ackDeadlineSeconds; + + /** + * Builder for {@code SubscriptionInfo} objects. + */ + public abstract static class Builder { + + /** + * Sets the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + @Deprecated + public abstract Builder name(String name); + + /** + * Sets the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public abstract Builder setName(String name); + + /** + * Sets the topic the subscription refers to, given the topic name. The topic is assumed to + * reside in the {@link PubSubOptions#getProjectId()} project. + */ + @Deprecated + public abstract Builder topic(String topic); + + /** + * Sets the topic the subscription refers to, given the topic name. The topic is assumed to + * reside in the {@link PubSubOptions#getProjectId()} project. + */ + public abstract Builder setTopic(String topic); + + /** + * Sets the topic the subscription refers to, given the project and topic names. + */ + @Deprecated + public abstract Builder topic(String project, String topic); + + /** + * Sets the topic the subscription refers to, given the project and topic names. + */ + public abstract Builder setTopic(String project, String topic); + + /** + * Sets the topic the subscription refers to, given the topic identity. If + * {@code topic.project()} is {@code null} the topic is assumed to reside in the + * {@link PubSubOptions#getProjectId()} project. + */ + @Deprecated + public abstract Builder topic(TopicId topic); + + /** + * Sets the topic the subscription refers to, given the topic identity. If + * {@code topic.project()} is {@code null} the topic is assumed to reside in the + * {@link PubSubOptions#getProjectId()} project. + */ + public abstract Builder setTopic(TopicId topic); + + /** + * Sets the push configuration for the subscription. If set, the subscription will be in + * push mode and the {@code pushConfig} parameter provides the push endpoint. If not set, the + * subscription will be in pull mode. + */ + @Deprecated + public abstract Builder pushConfig(PushConfig pushConfig); + + /** + * Sets the push configuration for the subscription. If set, the subscription will be in + * push mode and the {@code pushConfig} parameter provides the push endpoint. If not set, the + * subscription will be in pull mode. + */ + public abstract Builder setPushConfig(PushConfig pushConfig); + + /** + * Sets the maximum time after a subscriber receives a message before the subscriber should + * acknowledge the message. After message delivery but before the ack deadline expires and + * before the message is acknowledged, it is an outstanding message and will not be delivered + * again during that time (on a best-effort basis). For pull subscriptions, this value is used + * as the initial value for the ack deadline. To override the ack deadline value for a given + * message, use {@link PubSub#modifyAckDeadline(String, int, TimeUnit, Iterable)}. For push + * delivery, this value is used to set the request timeout for the call to the push endpoint. + * This value must be between 10 and 600 seconds, if not specified, 10 seconds is used. + */ + @Deprecated + public abstract Builder ackDeadLineSeconds(int ackDeadLineSeconds); + + /** + * Sets the maximum time after a subscriber receives a message before the subscriber should + * acknowledge the message. After message delivery but before the ack deadline expires and + * before the message is acknowledged, it is an outstanding message and will not be delivered + * again during that time (on a best-effort basis). For pull subscriptions, this value is used + * as the initial value for the ack deadline. To override the ack deadline value for a given + * message, use {@link PubSub#modifyAckDeadline(String, int, TimeUnit, Iterable)}. For push + * delivery, this value is used to set the request timeout for the call to the push endpoint. + * This value must be between 10 and 600 seconds, if not specified, 10 seconds is used. + */ + public abstract Builder setAckDeadLineSeconds(int ackDeadLineSeconds); + + /** + * Creates a subscription object. + */ + public abstract SubscriptionInfo build(); + } + + static final class BuilderImpl extends Builder { + + private String name; + private TopicId topic; + private PushConfig pushConfig; + private int ackDeadlineSeconds; + + private BuilderImpl(TopicId topic, String name) { + this.topic = checkNotNull(topic); + this.name = checkNotNull(name); + } + + BuilderImpl(SubscriptionInfo subscription) { + name = subscription.name; + topic = subscription.topic; + pushConfig = subscription.pushConfig; + ackDeadlineSeconds = subscription.ackDeadlineSeconds; + } + + @Override + @Deprecated + public Builder name(String name) { + return setName(name); + } + + @Override + public Builder setName(String name) { + this.name = checkNotNull(name); + return this; + } + + @Override + @Deprecated + public Builder topic(String project, String topic) { + return setTopic(project, topic); + } + + @Override + public Builder setTopic(String project, String topic) { + return setTopic(TopicId.of(checkNotNull(project), topic)); + } + + @Override + @Deprecated + public Builder topic(String topic) { + return setTopic(topic); + } + + @Override + public Builder setTopic(String topic) { + return setTopic(TopicId.of(topic)); + } + + @Override + @Deprecated + public Builder topic(TopicId topic) { + return setTopic(topic); + } + + @Override + public Builder setTopic(TopicId topic) { + this.topic = checkNotNull(topic); + return this; + } + + @Override + @Deprecated + public Builder pushConfig(PushConfig pushConfig) { + return setPushConfig(pushConfig); + } + + @Override + public Builder setPushConfig(PushConfig pushConfig) { + this.pushConfig = pushConfig; + return this; + } + + @Override + @Deprecated + public Builder ackDeadLineSeconds(int ackDeadlineSeconds) { + return setAckDeadLineSeconds(ackDeadlineSeconds); + } + + @Override + public Builder setAckDeadLineSeconds(int ackDeadlineSeconds) { + this.ackDeadlineSeconds = ackDeadlineSeconds; + return this; + } + + @Override + public SubscriptionInfo build() { + return new SubscriptionInfo(this); + } + } + + SubscriptionInfo(BuilderImpl builder) { + topic = builder.topic; + name = builder.name; + pushConfig = builder.pushConfig; + ackDeadlineSeconds = builder.ackDeadlineSeconds; + } + + /** + * Returns the identity of the topic this subscription refers to. If {@link TopicId#project()} is + * {@code null} the topic is assumed to reside in the {@link PubSubOptions#getProjectId()} + * project. After a topic is deleted, existing subscriptions to that topic are not deleted, but + * their topic field is set to {@link TopicId#deletedTopic()}. + */ + @Deprecated + public TopicId topic() { + return getTopic(); + } + + /** + * Returns the identity of the topic this subscription refers to. If {@link TopicId#project()} is + * {@code null} the topic is assumed to reside in the {@link PubSubOptions#getProjectId()} + * project. After a topic is deleted, existing subscriptions to that topic are not deleted, but + * their topic field is set to {@link TopicId#deletedTopic()}. + */ + public TopicId getTopic() { + return topic; + } + + /** + * Returns the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + @Deprecated + public String name() { + return getName(); + } + + /** + * Returns the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public String getName() { + return name; + } + + /** + * Returns the push configuration for the subscription. If set, the subscription is in push mode + * and the returned value defines the push endpoint. If {@code null}, the subscription is in pull + * mode. + */ + @Deprecated + public PushConfig pushConfig() { + return getPushConfig(); + } + + /** + * Returns the push configuration for the subscription. If set, the subscription is in push mode + * and the returned value defines the push endpoint. If {@code null}, the subscription is in pull + * mode. + */ + public PushConfig getPushConfig() { + return pushConfig; + } + + /** + * Returns the maximum time after a subscriber receives a message before the subscriber should + * acknowledge the message. After message delivery but before the ack deadline expires and + * before the message is acknowledged, it is an outstanding message and will not be delivered + * again during that time (on a best-effort basis). For pull subscriptions, this value is used + * as the initial value for the ack deadline. To override the ack deadline value for a given + * message, use {@link PubSub#modifyAckDeadline(String, int, TimeUnit, Iterable)}. For push + * delivery, this value is used to set the request timeout for the call to the push endpoint. This + * value must be between 10 and 600 seconds, if not specified, 10 seconds is used. + */ + @Deprecated + public long ackDeadlineSeconds() { + return getAckDeadlineSeconds(); + } + + /** + * Returns the maximum time after a subscriber receives a message before the subscriber should + * acknowledge the message. After message delivery but before the ack deadline expires and + * before the message is acknowledged, it is an outstanding message and will not be delivered + * again during that time (on a best-effort basis). For pull subscriptions, this value is used + * as the initial value for the ack deadline. To override the ack deadline value for a given + * message, use {@link PubSub#modifyAckDeadline(String, int, TimeUnit, Iterable)}. For push + * delivery, this value is used to set the request timeout for the call to the push endpoint. This + * value must be between 10 and 600 seconds, if not specified, 10 seconds is used. + */ + public long getAckDeadlineSeconds() { + return ackDeadlineSeconds; + } + + final boolean baseEquals(SubscriptionInfo subscriptionInfo) { + return Objects.equals(topic, subscriptionInfo.topic) + && Objects.equals(name, subscriptionInfo.name) + && Objects.equals(pushConfig, subscriptionInfo.pushConfig) + && ackDeadlineSeconds == subscriptionInfo.ackDeadlineSeconds; + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj != null + && obj.getClass().equals(SubscriptionInfo.class) + && baseEquals((SubscriptionInfo) obj); + } + + @Override + public int hashCode() { + return Objects.hash(topic, name, pushConfig, ackDeadlineSeconds); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("topic", topic) + .add("name", name) + .add("pushConfig", pushConfig) + .add("ackDeadlineSeconds", ackDeadlineSeconds) + .toString(); + } + + com.google.pubsub.v1.Subscription toPb(String projectId) { + com.google.pubsub.v1.Subscription.Builder builder = + com.google.pubsub.v1.Subscription.newBuilder(); + builder.setTopic(topic.toPb(projectId)); + builder.setNameWithSubscriptionName(SubscriptionName.create(projectId, name)); + builder.setAckDeadlineSeconds(ackDeadlineSeconds); + if (pushConfig != null) { + builder.setPushConfig(pushConfig.toPb()); + } + return builder.build(); + } + + static SubscriptionInfo fromPb(com.google.pubsub.v1.Subscription subscription) { + Builder builder = newBuilder(TopicId.fromPb(subscription.getTopic()), + subscription.getNameAsSubscriptionName().getSubscription()); + builder.setAckDeadLineSeconds(subscription.getAckDeadlineSeconds()); + // A subscription with an "empty" push config is a pull subscription + if (subscription.hasPushConfig() + && !subscription.getPushConfig().getPushEndpoint().equals("")) { + builder.setPushConfig(PushConfig.fromPb(subscription.getPushConfig())); + } + return builder.build(); + } + + /** + * Returns a builder for the subscription object. + */ + public Builder toBuilder() { + return new BuilderImpl(this); + } + + /** + * Creates a pull {@code SubscriptionInfo} object given the name of the topic and the name of the + * subscription. The topic is assumed to reside in the {@link PubSubOptions#getProjectId()} + * project. + * + * @param topic the name of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public static SubscriptionInfo of(String topic, String name) { + return newBuilder(topic, name).build(); + } + + /** + * Creates a pull {@code SubscriptionInfo} object given the identity of the topic and the name of + * the subscription. If {@code topic.project()} is {@code null} the topic is assumed to reside in + * the {@link PubSubOptions#getProjectId()} project. + * + * @param topic the identity of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public static SubscriptionInfo of(TopicId topic, String name) { + return newBuilder(topic, name).build(); + } + + /** + * Creates a push {@code SubscriptionInfo} object given the name of the topic, the name of the + * subscription and the push endpoint. The topic is assumed to reside in the + * {@link PubSubOptions#getProjectId()} project. + * + * @param topic the name of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + * @param endpoint a URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + */ + public static SubscriptionInfo of(String topic, String name, String endpoint) { + return newBuilder(topic, name).setPushConfig(PushConfig.of(endpoint)).build(); + } + + /** + * Creates a push {@code SubscriptionInfo} object given the identity of the topic, the name of the + * subscription and the push endpoint. If {@code topic.project()} is {@code null} the topic is + * assumed to reside in the {@link PubSubOptions#getProjectId()} project. + * + * @param topic the identity of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + * @param endpoint a URL locating the endpoint to which messages should be pushed. For example, + * an endpoint might use {@code https://example.com/push}. + */ + public static SubscriptionInfo of(TopicId topic, String name, String endpoint) { + return newBuilder(topic, name).setPushConfig(PushConfig.of(endpoint)).build(); + } + + /** + * Creates a builder for {@code SubscriptionInfo} objects given the name of the topic and the name + * of the subscription. The topic is assumed to reside in the {@link PubSubOptions#getProjectId()} + * project. + * + * @param topic the name of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + @Deprecated + public static Builder builder(String topic, String name) { + return newBuilder(topic, name); + } + + /** + * Creates a builder for {@code SubscriptionInfo} objects given the name of the topic and the name + * of the subscription. The topic is assumed to reside in the {@link PubSubOptions#getProjectId()} + * project. + * + * @param topic the name of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public static Builder newBuilder(String topic, String name) { + return newBuilder(TopicId.of(topic), name); + } + + /** + * Creates a builder for {@code SubscriptionInfo} objects given the identity of the topic and the + * name of the subscription. If {@code topic.project()} is {@code null} the topic is assumed to + * reside in the {@link PubSubOptions#getProjectId()} project. + * + * @param topic the identity of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + @Deprecated + public static Builder builder(TopicId topic, String name) { + return newBuilder(topic, name); + } + + /** + * Creates a builder for {@code SubscriptionInfo} objects given the identity of the topic and the + * name of the subscription. If {@code topic.project()} is {@code null} the topic is assumed to + * reside in the {@link PubSubOptions#getProjectId()} project. + * + * @param topic the identity of the topic the subscription refers to + * @param name the name of the subscription. The name must start with a letter, and contain only + * letters ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores + * ({@code _}), periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs + * ({@code %}). It must be between 3 and 255 characters in length and cannot begin with the + * string {@code goog}. + */ + public static Builder newBuilder(TopicId topic, String name) { + return new BuilderImpl(topic, name); + } +} 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 new file mode 100644 index 000000000000..3a5a8dd7c58c --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/Topic.java @@ -0,0 +1,556 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.cloud.AsyncPage; +import com.google.cloud.Page; +import com.google.cloud.Policy; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.common.base.Function; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Future; + +/** + * A Google Cloud Pub/Sub topic. A topic is a named resource to which messages are sent by + * publishers. Subscribers can receive messages sent to a topic by creating subscriptions. + * {@code Topic} adds a layer of service-related functionality over {@link TopicInfo}. Objects of + * this class are immutable. To get a {@code Topic} object with the most recent information use + * {@link #reload} or {@link #reloadAsync}. + * + * @see Pub/Sub Data Model + */ +public class Topic extends TopicInfo { + + private static final long serialVersionUID = -2686692223763315944L; + + private final PubSubOptions options; + private transient PubSub pubsub; + + /** + * A builder for {@code Topic} objects. + */ + public static final class Builder extends TopicInfo.Builder { + + private final PubSub pubsub; + private final BuilderImpl delegate; + + private Builder(Topic topic) { + pubsub = topic.pubsub; + delegate = new BuilderImpl(topic); + } + + @Override + @Deprecated + public Builder name(String name) { + return setName(name); + } + + @Override + public Builder setName(String name) { + delegate.setName(name); + return this; + } + + @Override + public Topic build() { + return new Topic(this.pubsub, this.delegate); + } + } + + Topic(PubSub pubsub, BuilderImpl builder) { + super(builder); + this.pubsub = checkNotNull(pubsub); + options = pubsub.getOptions(); + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public final int hashCode() { + return Objects.hash(options, super.hashCode()); + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !obj.getClass().equals(Topic.class)) { + return false; + } + Topic other = (Topic) obj; + return baseEquals(other) && Objects.equals(options, other.options); + } + + /** + * Returns the topic's {@code PubSub} object used to issue requests. + */ + @Deprecated + public PubSub pubSub() { + return getPubsub(); + } + + /** + * Returns the topic's {@code PubSub} object used to issue requests. + */ + public PubSub getPubsub() { + return 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 + */ + public boolean delete() { + return pubsub.deleteTopic(getName()); + } + + /** + * Sends a request for deleting this topic. This method returns a {@code Future} object to consume + * 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() { + return pubsub.deleteTopicAsync(getName()); + } + + /** + * 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 + */ + public Topic reload() { + return pubsub.getTopic(getName()); + } + + /** + * Sends a request to fetch current topic's latest information. This method returns a + * {@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() { + return pubsub.getTopicAsync(getName()); + } + + /** + * 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 + * payload and no attributes + */ + public String publish(Message message) { + return pubsub.publish(getName(), message); + } + + /** + * Sends a request for publishing a message to the this topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a service-generated + * 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 + */ + public Future publishAsync(Message message) { + return pubsub.publishAsync(getName(), message); + } + + /** + * Publishes a number of messages to this topic. This method returns a list of service-generated + * 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. + * @throws PubSubException upon failure, if the topic does not exist or if one of the messages has + * empty payload and no attributes + */ + public List publish(Message message, Message... messages) { + return pubsub.publish(getName(), message, messages); + } + + /** + * Sends a request to publish a number of messages to this topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a list of + * 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 + * the messages. + */ + public Future> publishAsync(Message message, Message... messages) { + return pubsub.publishAsync(getName(), message, messages); + } + + /** + * Publishes a number of messages to this topic. This method returns a list ofservice-generated + * 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 + * empty payload and no attributes + */ + public List publish(Iterable messages) { + return pubsub.publish(getName(), messages); + } + + /** + * Sends a request to publish a number of messages to this topic. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns a list of + * 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. + */ + public Future> publishAsync(Iterable messages) { + return pubsub.publishAsync(getName(), messages); + } + + /** + * Lists the identities of the subscriptions for this topic. This method returns a {@link Page} + * 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) { + return pubsub.listSubscriptions(getName(), options); + } + + /** + * Sends a request for listing the identities of subscriptions for this topic. This method returns + * a {@code Future} object to consume the result. {@link Future#get()} returns an + * {@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(getName(), options); + } + + /** + * 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() { + return pubsub.getTopicPolicy(this.getName()); + } + + /** + * Sends a request for getting the IAM access control policy for this topic. This method returns a + * {@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() { + return pubsub.getTopicPolicyAsync(this.getName()); + } + + /** + * Sets the IAM access control policy for this topic. Replaces any existing policy. This method + * returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, a + * {@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) { + return pubsub.replaceTopicPolicy(this.getName(), newPolicy); + } + + /** + * Sends a request to set the IAM access control policy for this topic. Replaces any existing + * policy. This method returns a {@code Future} object to consume the result. {@link Future#get()} + * returns the new policy. + * + *

    It is recommended that you use the read-modify-write pattern. This pattern entails reading + * the project's current policy, updating it locally, and then sending the modified policy for + * writing. Cloud IAM solves the problem of conflicting processes simultaneously attempting to + * modify a policy by using the {@link Policy#etag etag} property. This property is used to + * verify whether the policy has changed since the last request. When you make a request with an + * etag value, the value in the request is compared with the existing etag value associated with + * the policy. The policy is written only if the etag values match. If the etags don't match, + * {@link Future#get()} will throw a {@link java.util.concurrent.ExecutionException} caused by a + * {@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) { + return pubsub.replaceTopicPolicyAsync(this.getName(), newPolicy); + } + + /** + * Returns the permissions that a caller has on this topic. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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 + * @see + * Permissions and Roles + */ + public List testPermissions(List permissions) { + return pubsub.testTopicPermissions(this.getName(), permissions); + } + + /** + * Sends a request to get the permissions that a caller has on this topic. + * + *

    You typically don't call this method if you're using Google Cloud Platform directly to + * manage permissions. This method is intended for integration with your proprietary software, + * 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) + * @throws PubSubException upon failure + * @see + * Permissions and Roles + */ + public Future> testPermissionsAsync(List permissions) { + return pubsub.testTopicPermissionsAsync(this.getName(), permissions); + } + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + input.defaultReadObject(); + this.pubsub = options.getService(); + } + + static Topic fromPb(PubSub pubsub, com.google.pubsub.v1.Topic topicPb) { + TopicInfo topicInfo = TopicInfo.fromPb(topicPb); + return new Topic(pubsub, new BuilderImpl(topicInfo)); + } + + static Function fromPbFunction(final PubSub pubsub) { + return new Function() { + @Override + public Topic apply(com.google.pubsub.v1.Topic topicPb) { + return topicPb != null ? fromPb(pubsub, topicPb) : null; + } + }; + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicId.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicId.java new file mode 100644 index 000000000000..a121ca11c4e1 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicId.java @@ -0,0 +1,154 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; + +import com.google.pubsub.v1.TopicName; +import java.io.Serializable; +import java.util.Objects; + +/** + * Identity for a Google PubSub topic. A {@code TopicId} object can be used to create subscriptions + * for topics that possibly reside in different projects. + */ +public final class TopicId implements Serializable { + + private static final long serialVersionUID = -4913169763174877777L; + private static final String DELETED_TOPIC_NAME = "_deleted-topic_"; + private static final TopicId DELETED_TOPIC = new TopicId(null, DELETED_TOPIC_NAME, true); + + private final String project; + private final String topic; + private final boolean isDeleted; + + private TopicId(String project, String topic, boolean isDeleted) { + this.project = project; + this.topic = checkNotNull(topic); + this.isDeleted = isDeleted; + } + + private TopicId(String project, String topic) { + this(project, topic, false); + } + + /** + * Returns the name of the project where the topic resides. If {@code null} the topic is assumed + * to reside in the {@link PubSubOptions#getProjectId()} project. + */ + @Deprecated + public String project() { + return getProject(); + } + + /** + * Returns the name of the project where the topic resides. If {@code null} the topic is assumed + * to reside in the {@link PubSubOptions#getProjectId()} project. + */ + public String getProject() { + return project; + } + + /** + * Returns the name of the topic. + */ + @Deprecated + public String topic() { + return getTopic(); + } + + /** + * Returns the name of the topic. + */ + public String getTopic() { + return topic; + } + + /** + * Returns {@code true} if this object is the identity of a deleted topic, {@code false} + * otherwhise. If {@code isDeleted()} is {@code true}, {@link #topic()} returns + * "{@code _deleted-topic_}" and {@link #project()} returns {@code null}. + */ + public boolean isDeleted() { + return isDeleted; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("project", project) + .add("topic", topic) + .add("isDeleted", isDeleted) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(project, topic, isDeleted); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TopicId)) { + return false; + } + TopicId other = (TopicId) obj; + return Objects.equals(project, other.project) + && Objects.equals(topic, other.topic) + && Objects.equals(isDeleted, other.isDeleted); + } + + String toPb(String projectId) { + return TopicName.create(project != null ? project : projectId, topic).toString(); + } + + /** + * Returns the identity of a deleted topic. The deleted topic is such that {@link #isDeleted()} + * returns {@code true}, {@link #topic()} returns "{@code _is_deleted_}" and {@link #project()} + * returns {@code null}. + */ + public static TopicId deletedTopic() { + return DELETED_TOPIC; + } + + /** + * Returns a topic identity given the topic name. + */ + public static TopicId of(String topic) { + return new TopicId(null, topic); + } + + /** + * Returns a topic identity given project and topic names. + */ + public static TopicId of(String project, String topic) { + return new TopicId(project, topic); + } + + static TopicId fromPb(String pb) { + if (Objects.equals(pb, DELETED_TOPIC_NAME)) { + return DELETED_TOPIC; + } + TopicName topicName = TopicName.parse(pb); + return TopicId.of(topicName.getProject(), topicName.getTopic()); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java new file mode 100644 index 000000000000..d669e1b94846 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java @@ -0,0 +1,201 @@ +/* + * 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.pubsub; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; +import com.google.pubsub.v1.TopicName; +import java.io.Serializable; +import java.util.Objects; + +/** + * A Google Cloud Pub/Sub topic. A topic is a named resource to which messages are sent by + * publishers. Subscribers can receive messages sent to a topic by creating subscriptions. + * + * @see Pub/Sub Data Model + */ +public class TopicInfo implements Serializable { + + private static final long serialVersionUID = -5907624842808725353L; + + private final String name; + + /** + * Builder for {@code TopicInfo} objects. + */ + public abstract static class Builder { + + /** + * Sets the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). It + * must be between 3 and 255 characters in length and cannot begin with the string {@code goog}. + */ + @Deprecated + public abstract Builder name(String name); + + /** + * Sets the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). It + * must be between 3 and 255 characters in length and cannot begin with the string {@code goog}. + */ + public abstract Builder setName(String name); + + /** + * Creates a topic object. + */ + public abstract TopicInfo build(); + } + + static final class BuilderImpl extends Builder { + + private String name; + + BuilderImpl(String name) { + this.name = checkNotNull(name); + } + + BuilderImpl(TopicInfo topicInfo) { + this.name = topicInfo.name; + } + + @Override + @Deprecated + public Builder name(String name) { + return setName(name); + } + + @Override + public Builder setName(String name) { + this.name = checkNotNull(name); + return this; + } + + @Override + public TopicInfo build() { + return new TopicInfo(this); + } + } + + TopicInfo(BuilderImpl builder) { + name = builder.name; + } + + /** + * Returns the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). It + * must be between 3 and 255 characters in length and cannot begin with the string {@code goog}. + */ + @Deprecated + public String name() { + return getName(); + } + + /** + * Returns the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). It + * must be between 3 and 255 characters in length and cannot begin with the string {@code goog}. + */ + public String getName() { + return name; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + final boolean baseEquals(TopicInfo topicInfo) { + return Objects.equals(name, topicInfo.name); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj != null + && obj.getClass().equals(TopicInfo.class) + && baseEquals((TopicInfo) obj); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .toString(); + } + + com.google.pubsub.v1.Topic toPb(String projectId) { + return com.google.pubsub.v1.Topic.newBuilder() + .setNameWithTopicName(TopicName.create(projectId, name)) + .build(); + } + + static TopicInfo fromPb(com.google.pubsub.v1.Topic topicPb) { + return newBuilder(topicPb.getNameAsTopicName().getTopic()).build(); + } + + /** + * Returns a builder for the topic object. + */ + public Builder toBuilder() { + return new BuilderImpl(this); + } + + /** + * Creates a {@code TopicInfo} object given the name of the topic. + * + * @param name the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). + * It must be between 3 and 255 characters in length and cannot begin with the string + * {@code goog}. + */ + public static TopicInfo of(String name) { + return newBuilder(name).build(); + } + + /** + * Creates a builder for {@code TopicInfo} objects given the name of the topic. + * + * @param name the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). + * It must be between 3 and 255 characters in length and cannot begin with the string + * {@code goog}. + */ + @Deprecated + public static Builder builder(String name) { + return newBuilder(name); + } + + /** + * Creates a builder for {@code TopicInfo} objects given the name of the topic. + * + * @param name the name of the topic. The name must start with a letter, and contain only letters + * ({@code [A-Za-z]}), numbers ({@code [0-9]}), dashes ({@code -}), underscores ({@code _}), + * periods ({@code .}), tildes ({@code ~}), plus ({@code +}) or percent signs ({@code %}). + * It must be between 3 and 255 characters in length and cannot begin with the string + * {@code goog}. + */ + public static Builder newBuilder(String name) { + return new BuilderImpl(name); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java new file mode 100644 index 000000000000..c98a5f5c8820 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/** + * A client to Google Cloud Pub/Sub. + * + *

    Here's a simple usage example for using google-cloud from Compute Engine/App Engine Flexible. + * This example shows how to create a Pub/Sub topic and asynchronously publish messages to it. For + * the complete source code see + * + * CreateTopicAndPublishMessages.java. + *

     {@code
    + * try (PubSub pubsub = PubSubOptions.getDefaultInstance().getService()) {
    + *   Topic topic = pubsub.create(TopicInfo.of("test-topic"));
    + *   Message message1 = Message.of("First message");
    + *   Message message2 = Message.of("Second message");
    + *   topic.publishAsync(message1, message2);
    + * }}
    + * + *

    This second example shows how to create a Pub/Sub pull subscription and asynchronously pull + * messages from it. For the complete source code see + * + * CreateSubscriptionAndPullMessages.java. + *

     {@code
    + * try (PubSub pubsub = PubSubOptions.getDefaultInstance().getService()) {
    + *   Subscription subscription =
    + *   pubsub.create(SubscriptionInfo.of("test-topic", "test-subscription"));
    + *   MessageProcessor callback = new MessageProcessor() {
    + *     public void process(Message message) throws Exception {
    + *       System.out.printf("Received message \"%s\"%n", message.payloadAsString());
    + *     }
    + *   };
    + *   // Create a message consumer and pull messages (for 60 seconds)
    + *   try (MessageConsumer consumer = subscription.pullAsync(callback)) {
    + *     Thread.sleep(60_000);
    + *   }
    + * }}
    + * + * @see Google Cloud Pub/Sub + */ +package com.google.cloud.pubsub; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java new file mode 100644 index 000000000000..4af17e458aeb --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java @@ -0,0 +1,309 @@ +/* + * 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.pubsub.spi; + +import com.google.api.gax.core.ForwardingRpcFuture; +import com.google.api.gax.core.Function; +import com.google.api.gax.core.RpcFuture; +import com.google.api.gax.core.RpcFutureCallback; +import com.google.api.gax.grpc.ApiException; +import com.google.api.gax.grpc.ChannelProvider; +import com.google.api.gax.grpc.ExecutorProvider; +import com.google.api.gax.grpc.FixedChannelProvider; +import com.google.api.gax.grpc.FixedExecutorProvider; +import com.google.api.gax.grpc.ProviderManager; +import com.google.api.gax.grpc.UnaryCallSettings; +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.NoCredentials; +import com.google.cloud.pubsub.PubSubException; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.spi.v1.PublisherClient; +import com.google.cloud.pubsub.spi.v1.PublisherSettings; +import com.google.cloud.pubsub.spi.v1.SubscriberClient; +import com.google.cloud.pubsub.spi.v1.SubscriberSettings; +import com.google.common.collect.Sets; +import com.google.iam.v1.GetIamPolicyRequest; +import com.google.iam.v1.Policy; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; +import com.google.protobuf.Empty; +import com.google.pubsub.v1.AcknowledgeRequest; +import com.google.pubsub.v1.DeleteSubscriptionRequest; +import com.google.pubsub.v1.DeleteTopicRequest; +import com.google.pubsub.v1.GetSubscriptionRequest; +import com.google.pubsub.v1.GetTopicRequest; +import com.google.pubsub.v1.ListSubscriptionsRequest; +import com.google.pubsub.v1.ListSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicSubscriptionsRequest; +import com.google.pubsub.v1.ListTopicSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicsRequest; +import com.google.pubsub.v1.ListTopicsResponse; +import com.google.pubsub.v1.ModifyAckDeadlineRequest; +import com.google.pubsub.v1.ModifyPushConfigRequest; +import com.google.pubsub.v1.PublishRequest; +import com.google.pubsub.v1.PublishResponse; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; +import com.google.pubsub.v1.Subscription; +import com.google.pubsub.v1.Topic; +import io.grpc.ManagedChannel; +import io.grpc.Status.Code; +import io.grpc.netty.NegotiationType; +import io.grpc.netty.NettyChannelBuilder; +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import org.joda.time.Duration; + +public class DefaultPubSubRpc implements PubSubRpc { + + private final PublisherClient publisherClient; + private final SubscriberClient subscriberClient; + private final SubscriberClient noTimeoutSubscriberClient; + private final ScheduledExecutorService executor; + private final ProviderManager providerManager; + private final ExecutorFactory executorFactory; + + private boolean closed; + + private static final class InternalPubSubOptions extends PubSubOptions { + + private static final long serialVersionUID = -7997372049256706185L; + + private InternalPubSubOptions(PubSubOptions options) { + super(options.toBuilder()); + } + + @Override + protected ExecutorFactory getExecutorFactory() { + return super.getExecutorFactory(); + } + + @Override + protected UnaryCallSettings.Builder getApiCallSettings() { + return super.getApiCallSettings(); + } + + @Override + protected ChannelProvider getChannelProvider() { + return super.getChannelProvider(); + } + } + + private static final class PullFutureImpl extends ForwardingRpcFuture + implements PullFuture { + PullFutureImpl(RpcFuture delegate) { + super(delegate); + } + + @Override + public void addCallback(final PullCallback callback) { + addCallback( + new RpcFutureCallback() { + @Override + public void onSuccess(PullResponse response) { + callback.success(response); + } + + @Override + public void onFailure(Throwable error) { + callback.failure(error); + } + }); + } + } + + public DefaultPubSubRpc(PubSubOptions options) throws IOException { + InternalPubSubOptions internalOptions = new InternalPubSubOptions(options); + executorFactory = internalOptions.getExecutorFactory(); + executor = executorFactory.get(); + try { + ExecutorProvider executorProvider = FixedExecutorProvider.create(executor); + ChannelProvider channelProvider; + // todo(mziccard): ChannelProvider should support null/absent credentials for testing + if (options.getHost().contains("localhost") + || options.getCredentials().equals(NoCredentials.getInstance())) { + ManagedChannel managedChannel = NettyChannelBuilder.forTarget(options.getHost()) + .negotiationType(NegotiationType.PLAINTEXT) + .executor(executor) + .build(); + channelProvider = FixedChannelProvider.create(managedChannel); + } else { + channelProvider = internalOptions.getChannelProvider(); + } + providerManager = ProviderManager.newBuilder() + .setChannelProvider(channelProvider) + .setExecutorProvider(executorProvider) + .build(); + UnaryCallSettings.Builder callSettingsBuilder = internalOptions.getApiCallSettings(); + PublisherSettings.Builder pubBuilder = PublisherSettings.defaultBuilder() + .setExecutorProvider(providerManager) + .setChannelProvider(providerManager) + .applyToAllUnaryMethods(callSettingsBuilder); + SubscriberSettings.Builder subBuilder = SubscriberSettings.defaultBuilder() + .setExecutorProvider(providerManager) + .setChannelProvider(providerManager) + .applyToAllUnaryMethods(callSettingsBuilder); + publisherClient = PublisherClient.create(pubBuilder.build()); + subscriberClient = SubscriberClient.create(subBuilder.build()); + callSettingsBuilder.setRetrySettingsBuilder(callSettingsBuilder.getRetrySettingsBuilder() + .setTotalTimeout(Duration.millis(Long.MAX_VALUE)) + .setInitialRpcTimeout(Duration.millis(Long.MAX_VALUE)) + .setMaxRpcTimeout(Duration.millis(Long.MAX_VALUE))); + subBuilder.applyToAllUnaryMethods(callSettingsBuilder); + noTimeoutSubscriberClient = SubscriberClient.create(subBuilder.build()); + } catch (Exception ex) { + throw new IOException(ex); + } + } + + private static RpcFuture translate( + RpcFuture from, final boolean idempotent, int... returnNullOn) { + final Set returnNullOnSet = Sets.newHashSetWithExpectedSize(returnNullOn.length); + for (int value : returnNullOn) { + returnNullOnSet.add(value); + } + return from.catching( + ApiException.class, + new Function() { + @Override + public V apply(ApiException exception) { + if (returnNullOnSet.contains(exception.getStatusCode().value())) { + return null; + } + throw new PubSubException(exception, idempotent); + } + }); + } + + @Override + public Future create(Topic topic) { + // TODO: it would be nice if we can get the idempotent information from the UnaryCallSettings + // or from the exception + return translate(publisherClient.createTopicCallable().futureCall(topic), true); + } + + @Override + public Future publish(PublishRequest request) { + return translate(publisherClient.publishCallable().futureCall(request), false); + } + + @Override + public Future get(GetTopicRequest request) { + return translate(publisherClient.getTopicCallable().futureCall(request), true, + Code.NOT_FOUND.value()); + } + + @Override + public Future list(ListTopicsRequest request) { + // we should consider using gax PageAccessor once + // https://github.com/googleapis/gax-java/issues/74 is fixed + // Though it is a cleaner SPI without it, but PageAccessor is an interface + // and if it saves code we should not easily dismiss it. + return translate(publisherClient.listTopicsCallable().futureCall(request), true); + } + + @Override + public Future list(ListTopicSubscriptionsRequest request) { + return translate(publisherClient.listTopicSubscriptionsCallable().futureCall(request), true); + } + + @Override + public Future delete(DeleteTopicRequest request) { + return translate(publisherClient.deleteTopicCallable().futureCall(request), true, + Code.NOT_FOUND.value()); + } + + @Override + public Future create(Subscription subscription) { + return translate(subscriberClient.createSubscriptionCallable().futureCall(subscription), false); + } + + @Override + public Future get(GetSubscriptionRequest request) { + return translate(subscriberClient.getSubscriptionCallable().futureCall(request), true, + Code.NOT_FOUND.value()); + } + + @Override + public Future list(ListSubscriptionsRequest request) { + return translate(subscriberClient.listSubscriptionsCallable().futureCall(request), true); + } + + @Override + public Future delete(DeleteSubscriptionRequest request) { + return translate(subscriberClient.deleteSubscriptionCallable().futureCall(request), true, + Code.NOT_FOUND.value()); + } + + @Override + public Future modify(ModifyAckDeadlineRequest request) { + return translate(subscriberClient.modifyAckDeadlineCallable().futureCall(request), false); + } + + @Override + public Future acknowledge(AcknowledgeRequest request) { + return translate(subscriberClient.acknowledgeCallable().futureCall(request), false); + } + + private static PullFuture pull(SubscriberClient subscriberClient, PullRequest request) { + return new PullFutureImpl(translate(subscriberClient.pullCallable().futureCall(request), false)); + } + + @Override + public PullFuture pull(PullRequest request) { + return request.getReturnImmediately() + ? pull(subscriberClient, request) : pull(noTimeoutSubscriberClient, request); + } + + @Override + public Future modify(ModifyPushConfigRequest request) { + return translate(subscriberClient.modifyPushConfigCallable().futureCall(request), false); + } + + @Override + public Future getIamPolicy(String resource) { + GetIamPolicyRequest request = GetIamPolicyRequest.newBuilder().setResource(resource).build(); + return translate(subscriberClient.getIamPolicyCallable().futureCall(request), true, + Code.NOT_FOUND.value()); + } + + @Override + public Future setIamPolicy(SetIamPolicyRequest request) { + return translate(subscriberClient.setIamPolicyCallable().futureCall(request), false); + } + + @Override + public Future testIamPermissions(TestIamPermissionsRequest request) { + return translate(subscriberClient.testIamPermissionsCallable().futureCall(request), true); + } + + @Override + public void close() throws Exception { + if (closed) { + return; + } + closed = true; + subscriberClient.close(); + noTimeoutSubscriberClient.close(); + publisherClient.close(); + providerManager.getChannel().shutdown(); + executorFactory.release(executor); + } +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java new file mode 100644 index 000000000000..30a5688b01c5 --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java @@ -0,0 +1,228 @@ +/* + * 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.pubsub.spi; + +import com.google.iam.v1.Policy; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; +import com.google.protobuf.Empty; +import com.google.pubsub.v1.AcknowledgeRequest; +import com.google.pubsub.v1.DeleteSubscriptionRequest; +import com.google.pubsub.v1.DeleteTopicRequest; +import com.google.pubsub.v1.GetSubscriptionRequest; +import com.google.pubsub.v1.GetTopicRequest; +import com.google.pubsub.v1.ListSubscriptionsRequest; +import com.google.pubsub.v1.ListSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicSubscriptionsRequest; +import com.google.pubsub.v1.ListTopicSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicsRequest; +import com.google.pubsub.v1.ListTopicsResponse; +import com.google.pubsub.v1.ModifyAckDeadlineRequest; +import com.google.pubsub.v1.ModifyPushConfigRequest; +import com.google.pubsub.v1.PublishRequest; +import com.google.pubsub.v1.PublishResponse; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; +import com.google.pubsub.v1.Subscription; +import com.google.pubsub.v1.Topic; +import java.util.concurrent.Future; + +public interface PubSubRpc extends AutoCloseable { + + /** + * A callback that can be registered to {@link PullFuture} objects. Objects of this class allow + * to asynchronously react to the success or failure of a pull RPC. + */ + interface PullCallback { + + /** + * This method is invoked with the result of a {@link PullFuture} when it was successful. + * + * @param response the pull response + */ + void success(PullResponse response); + + /** + * This method is invoked when the {@link PullFuture} failed or was cancelled. + * + * @param error the execption that caused the {@link PullFuture} to fail + */ + void failure(Throwable error); + } + + /** + * A {@link Future} implementation for pull RPCs. This class also allows users to register + * callbacks via {@link #addCallback(PullCallback)}. + */ + interface PullFuture extends Future { + + /** + * Registers a callback to be run on the given executor. The listener will run when the pull + * future completed its computation or, if the computation is already complete, immediately. + * There is no guaranteed ordering of execution of callbacks. + * + *

    Registered callbacks are run using the same thread that run the RPC call. Only lightweight + * callbacks should be registered via this method. + */ + void addCallback(final PullCallback callback); + } + + /** + * Sends a request to create a topic. This method returns a {@code Future} object to consume the + * result. {@link Future#get()} returns the created topic. + * + * @param topic the topic to create + */ + Future create(Topic topic); + + /** + * Sends a request to publish messages. This method returns a {@code Future} object to consume the + * result. {@link Future#get()} returns a response object containing the publish result. + * + * @param request the request object containing all of the parameters for the API call + */ + Future publish(PublishRequest request); + + /** + * Sends a request to get a topic. This method returns a {@code Future} object to consume the + * result. {@link Future#get()} returns the requested topic or {@code null} if not found. + * + * @param request the request object containing all of the parameters for the API call + */ + Future get(GetTopicRequest request); + + /** + * Sends a request to list the topics in a project. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns a response object containing the listing + * result. + * + * @param request the request object containing all of the parameters for the API call + */ + Future list(ListTopicsRequest request); + + /** + * Sends a request to list the subscriptions for a topic. This method returns a {@code Future} + * object to consume the result. {@link Future#get()} returns a response object containing the + * listing result. + * + * @param request the request object containing all of the parameters for the API call + */ + Future list(ListTopicSubscriptionsRequest request); + + /** + * Sends a request to delete a topic. This method returns a {@code Future} object to consume the + * result. {@link Future#get()} returns {@link Empty#getDefaultInstance()} or {@code null} if the + * topic was not found. + * + * @param request the request object containing all of the parameters for the API call + */ + Future delete(DeleteTopicRequest request); + + /** + * Sends a request to create a subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns the created subscription. + * + * @param subscription the subscription to create + */ + Future create(Subscription subscription); + + /** + * Sends a request to get a subscription. This method returns a {@code Future} object to consume + * the result. {@link Future#get()} returns the requested subscription or {@code null} if not + * found. + * + * @param request the request object containing all of the parameters for the API call + */ + Future get(GetSubscriptionRequest request); + + /** + * Sends a request to list the subscriptions in a project. This method returns a {@code Future} + * object to consume the result. {@link Future#get()} returns a response object containing the + * listing result. + * + * @param request the request object containing all of the parameters for the API call + */ + Future list(ListSubscriptionsRequest request); + + /** + * Sends a request to delete a subscription. This method returns a {@code Future} object to + * consume the result. {@link Future#get()} returns {@link Empty#getDefaultInstance()} or + * {@code null} if the subscription was not found. + * + * @param request the request object containing all of the parameters for the API call + */ + Future delete(DeleteSubscriptionRequest request); + + /** + * Sends a request to modify the acknowledge deadline of a subscription. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns + * {@link Empty#getDefaultInstance()} if the request was issued correctly. + * + * @param request the request object containing all of the parameters for the API call + */ + Future modify(ModifyAckDeadlineRequest request); + + /** + * Sends a request to acknowledge messages for a subscription. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns + * {@link Empty#getDefaultInstance()} if the request was issued correctly. + * + * @param request the request object containing all of the parameters for the API call + */ + Future acknowledge(AcknowledgeRequest request); + + /** + * Sends a request to pull messages from a subscription. This method returns a {@link PullFuture} + * object to consume the result. {@link PullFuture#get()} returns a response object containing the + * pulled messages. {@link PullFuture#addCallback(PullCallback)} can be used to register a + * callback for the request's completion. + * + * @param request the request object containing all of the parameters for the API call + */ + PullFuture pull(PullRequest request); + + /** + * Sends a request to modify the push configuration of a subscription. This method returns a + * {@code Future} object to consume the result. {@link Future#get()} returns + * {@link Empty#getDefaultInstance()} if the request was issued correctly. + * + * @param request the request object containing all of the parameters for the API call + */ + Future modify(ModifyPushConfigRequest request); + + /** + * Sends a request to get the IAM policy for the provided resource. + * + * @param resource the resource for which to get the IAM policy + */ + Future getIamPolicy(String resource); + + /** + * Sends a request to set the IAM policy for a resource. + * + * @param request the request object containing all of the parameters for the API call + */ + Future setIamPolicy(SetIamPolicyRequest request); + + /** + * Sends a request to test the permissions that the caller has on a provided resource. + * + * @param request the request object containing all of the parameters for the API call + */ + Future testIamPermissions(TestIamPermissionsRequest request); +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java new file mode 100644 index 000000000000..d3648a68399f --- /dev/null +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java @@ -0,0 +1,27 @@ +/* + * 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.pubsub.spi; + +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.spi.ServiceRpcFactory; + +/** + * An interface for Pub/Sub RPC factory. + * Implementation will be loaded via {@link java.util.ServiceLoader}. + */ +public interface PubSubRpcFactory extends ServiceRpcFactory { +} diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/MessageDispatcher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/MessageDispatcher.java index c378d3e7e39c..84a073eb3d3e 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/MessageDispatcher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/MessageDispatcher.java @@ -16,7 +16,7 @@ package com.google.cloud.pubsub.spi.v1; -import com.google.api.gax.bundling.FlowController; +import com.google.api.gax.grpc.FlowController; import com.google.api.stats.Distribution; import com.google.cloud.Clock; import com.google.cloud.pubsub.spi.v1.MessageReceiver.AckReply; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PollingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PollingSubscriberConnection.java index bd0dbdb02c23..0975e3d6b70d 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PollingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PollingSubscriberConnection.java @@ -18,7 +18,7 @@ import static com.google.cloud.pubsub.spi.v1.StatusUtil.isRetryable; -import com.google.api.gax.bundling.FlowController; +import com.google.api.gax.grpc.FlowController; import com.google.api.stats.Distribution; import com.google.auth.Credentials; import com.google.cloud.Clock; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Publisher.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Publisher.java index 722d26f17051..6a476f695431 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Publisher.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Publisher.java @@ -16,7 +16,8 @@ package com.google.cloud.pubsub.spi.v1; -import com.google.api.gax.bundling.FlowController; +import com.google.api.gax.grpc.FlowControlSettings; +import com.google.api.gax.grpc.FlowController; import com.google.api.gax.core.RetrySettings; import com.google.api.gax.grpc.BundlingSettings; import com.google.api.gax.grpc.ChannelProvider; @@ -122,7 +123,7 @@ public static long getApiMaxRequestBytes() { private final RetrySettings retrySettings; private final LongRandom longRandom; - private final FlowController.Settings flowControlSettings; + private final FlowControlSettings flowControlSettings; private final boolean failOnFlowControlLimits; private final Lock messagesBundleLock; @@ -446,7 +447,7 @@ private long getMaxBundleBytes() { * The bundling settings configured on this {@code Publisher}. See {@link * #failOnFlowControlLimits()}. */ - public FlowController.Settings getFlowControlSettings() { + public FlowControlSettings getFlowControlSettings() { return flowControlSettings; } @@ -571,7 +572,7 @@ public long nextLong(long least, long bound) { BundlingSettings bundlingSettings = DEFAULT_BUNDLING_SETTINGS; // Client-side flow control options - FlowController.Settings flowControlSettings = FlowController.Settings.DEFAULT; + FlowControlSettings flowControlSettings = FlowControlSettings.getDefaultInstance(); boolean failOnFlowControlLimits = false; RetrySettings retrySettings = DEFAULT_RETRY_SETTINGS; @@ -606,17 +607,6 @@ public Builder setBundlingSettings(BundlingSettings bundlingSettings) { Preconditions.checkArgument(bundlingSettings.getRequestByteThreshold() > 0); Preconditions.checkNotNull(bundlingSettings.getDelayThreshold()); Preconditions.checkArgument(bundlingSettings.getDelayThreshold().getMillis() > 0); - - Preconditions.checkArgument( - bundlingSettings.getElementCountLimit() == null, - "elementCountLimit option not honored by current implementation"); - Preconditions.checkArgument( - bundlingSettings.getRequestByteLimit() == null, - "requestByteLimit option not honored by current implementation"); - Preconditions.checkArgument( - bundlingSettings.getBlockingCallCountThreshold() == null, - "blockingCallCountThreshold option not honored by current implementation"); - this.bundlingSettings = bundlingSettings; return this; } @@ -624,7 +614,7 @@ public Builder setBundlingSettings(BundlingSettings bundlingSettings) { // Flow control options /** Sets the flow control settings. */ - public Builder setFlowControlSettings(FlowController.Settings flowControlSettings) { + public Builder setFlowControlSettings(FlowControlSettings flowControlSettings) { this.flowControlSettings = Preconditions.checkNotNull(flowControlSettings); return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherClient.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherClient.java index 0b7ca15db947..e159bf016dc3 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherClient.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherClient.java @@ -300,8 +300,7 @@ public final UnaryCallable createTopicCallable() { * @param messages The messages to publish. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - /* package-private */ final PublishResponse publish( - TopicName topic, List messages) { + public final PublishResponse publish(TopicName topic, List messages) { PublishRequest request = PublishRequest.newBuilder().setTopicWithTopicName(topic).addAllMessages(messages).build(); @@ -365,7 +364,7 @@ public final PublishResponse publish(PublishRequest request) { * } *

    */ - /* package-private */ final UnaryCallable publishCallable() { + public final UnaryCallable publishCallable() { return publishCallable; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherSettings.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherSettings.java index 72e0d0128d62..db1a202392ab 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherSettings.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/PublisherSettings.java @@ -527,11 +527,8 @@ private static Builder createDefault() { .publishSettings() .getBundlingSettingsBuilder() .setElementCountThreshold(10) - .setElementCountLimit(1000) .setRequestByteThreshold(1024) - .setRequestByteLimit(10485760) - .setDelayThreshold(Duration.millis(10)) - .setBlockingCallCountThreshold(1); + .setDelayThreshold(Duration.millis(10)); builder .publishSettings() .setRetryableCodes(RETRYABLE_CODE_DEFINITIONS.get("one_plus_delivery")) diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/StreamingSubscriberConnection.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/StreamingSubscriberConnection.java index 59e97b7d0666..18dec23151ad 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/StreamingSubscriberConnection.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/StreamingSubscriberConnection.java @@ -18,7 +18,7 @@ import static com.google.cloud.pubsub.spi.v1.StatusUtil.isRetryable; -import com.google.api.gax.bundling.FlowController; +import com.google.api.gax.grpc.FlowController; import com.google.api.stats.Distribution; import com.google.auth.Credentials; import com.google.cloud.Clock; diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Subscriber.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Subscriber.java index 7a39b7c8c5a9..84840c5d24eb 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Subscriber.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/Subscriber.java @@ -16,7 +16,8 @@ package com.google.cloud.pubsub.spi.v1; -import com.google.api.gax.bundling.FlowController; +import com.google.api.gax.grpc.FlowControlSettings; +import com.google.api.gax.grpc.FlowController; import com.google.api.gax.grpc.ExecutorProvider; import com.google.api.gax.grpc.InstantiatingExecutorProvider; import com.google.api.stats.Distribution; @@ -148,7 +149,7 @@ public Duration getAckExpirationPadding() { } /** The flow control settings the Subscriber is configured with. */ - public FlowController.Settings getFlowControlSettings() { + public FlowControlSettings getFlowControlSettings() { return impl.flowControlSettings; } @@ -265,7 +266,7 @@ private static class SubscriberImpl extends AbstractService { private final SubscriptionName subscriptionName; private final String cachedSubscriptionNameString; - private final FlowController.Settings flowControlSettings; + private final FlowControlSettings flowControlSettings; private final Duration ackExpirationPadding; private final ScheduledExecutorService executor; private final Distribution ackLatencyDistribution = @@ -533,7 +534,7 @@ public static final class Builder { Duration ackExpirationPadding = DEFAULT_ACK_EXPIRATION_PADDING; - FlowController.Settings flowControlSettings = FlowController.Settings.DEFAULT; + FlowControlSettings flowControlSettings = FlowControlSettings.getDefaultInstance(); ExecutorProvider executorProvider = DEFAULT_EXECUTOR_PROVIDER; Optional>> channelBuilder = @@ -569,7 +570,7 @@ public Builder setChannelBuilder( } /** Sets the flow control settings. */ - public Builder setFlowControlSettings(FlowController.Settings flowControlSettings) { + public Builder setFlowControlSettings(FlowControlSettings flowControlSettings) { this.flowControlSettings = Preconditions.checkNotNull(flowControlSettings); return this; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/SubscriberClient.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/SubscriberClient.java index 23f723a79a31..53cf5d9d2f04 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/SubscriberClient.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/spi/v1/SubscriberClient.java @@ -814,7 +814,7 @@ public final UnaryCallable acknowledgeCallable() { * may return fewer than the number specified. * @throws com.google.api.gax.grpc.ApiException if the remote call fails */ - /* package-private */ final PullResponse pull( + public final PullResponse pull( SubscriptionName subscription, boolean returnImmediately, int maxMessages) { PullRequest request = @@ -875,7 +875,7 @@ public final PullResponse pull(PullRequest request) { * } *
    */ - /* package-private */ final UnaryCallable pullCallable() { + public final UnaryCallable pullCallable() { return pullCallable; } @@ -927,7 +927,7 @@ public final PullResponse pull(PullRequest request) { * } *
    */ - /* package-private */ final StreamingCallable + public final StreamingCallable streamingPullCallable() { return streamingPullCallable; } diff --git a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/testing/LocalPubSubHelper.java b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/testing/LocalPubSubHelper.java index b99eb2ab2885..67ee55e2a870 100644 --- a/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/testing/LocalPubSubHelper.java +++ b/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/testing/LocalPubSubHelper.java @@ -16,12 +16,16 @@ package com.google.cloud.pubsub.testing; -import com.google.cloud.ServiceOptions; +import com.google.cloud.NoCredentials; +import com.google.cloud.RetryParams; +import com.google.cloud.pubsub.PubSubOptions; import com.google.cloud.testing.BaseEmulatorHelper; import com.google.common.collect.ImmutableList; + import io.grpc.ManagedChannel; import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; + import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -31,12 +35,13 @@ import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.logging.Logger; + import org.joda.time.Duration; /** * A class that runs a Pubsub emulator instance for use in tests. */ -public class LocalPubSubHelper extends BaseEmulatorHelper { +public class LocalPubSubHelper extends BaseEmulatorHelper { private final List emulatorRunners; @@ -99,10 +104,18 @@ public ManagedChannel createChannel() { .build(); } - /** Returns a {@link ServiceOptions} describing the emulator. */ + /** + * Returns a {@link PubSubOptions} instance that sets the host to use the PubSub emulator on + * localhost. + */ @Override - public ServiceOptions getOptions() { - throw new UnsupportedOperationException("not implemented as PubSubOptions no longer exists"); + public PubSubOptions getOptions() { + return PubSubOptions.newBuilder() + .setProjectId(getProjectId()) + .setHost(DEFAULT_HOST + ":" + getPort()) + .setCredentials(NoCredentials.getInstance()) + .setRetryParams(RetryParams.noRetries()) + .build(); } /** diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/AckDeadlineRenewerTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/AckDeadlineRenewerTest.java new file mode 100644 index 000000000000..dbd1df110308 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/AckDeadlineRenewerTest.java @@ -0,0 +1,313 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertTrue; + +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.pubsub.spi.v1.FakeScheduledExecutorService; +import com.google.common.collect.ImmutableList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.joda.time.Duration; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +public class AckDeadlineRenewerTest { + + private static final int MIN_DEADLINE_MILLIS = 10_000; + private static final Duration TIME_ADVANCE = Duration.millis(9_000); + + private static final String SUBSCRIPTION1 = "subscription1"; + private static final String SUBSCRIPTION2 = "subscription2"; + private static final String ACK_ID1 = "ack-id1"; + private static final String ACK_ID2 = "ack-id2"; + private static final String ACK_ID3 = "ack-id3"; + + private PubSub pubsub; + private FakeScheduledExecutorService executorService; + private AckDeadlineRenewer ackDeadlineRenewer; + + @Rule + public Timeout globalTimeout = Timeout.seconds(60); + + @Before + public void setUp() { + pubsub = EasyMock.createStrictMock(PubSub.class); + executorService = new FakeScheduledExecutorService(); + ExecutorFactory executorFactory = new ExecutorFactory() { + @Override + public ExecutorService get() { + return executorService; + } + @Override + public void release(ExecutorService executor) { + executorService.shutdown(); + } + }; + PubSubOptions options = + PubSubOptions.newBuilder() + .setProjectId("projectId") + .setExecutorFactory(executorFactory) + .setClock(executorService.getClock()) + .build(); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.replay(pubsub); + ackDeadlineRenewer = new AckDeadlineRenewer(pubsub); + } + + @After + public void tearDown() throws Exception { + EasyMock.verify(pubsub); + ackDeadlineRenewer.close(); + } + + private IAnswer> createAnswer(final CountDownLatch latch, + final AtomicLong renewal) { + return new IAnswer>() { + @Override + public Future answer() throws Throwable { + latch.countDown(); + renewal.set(executorService.getClock().millis()); + return null; + } + }; + } + + @Test + public void testAddOneMessage() throws InterruptedException { + EasyMock.reset(pubsub); + final CountDownLatch firstLatch = new CountDownLatch(1); + final CountDownLatch secondLatch = new CountDownLatch(1); + final AtomicLong firstRenewal = new AtomicLong(); + final AtomicLong secondRenewal = new AtomicLong(); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(firstLatch, firstRenewal)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(secondLatch, secondRenewal)); + EasyMock.replay(pubsub); + long addTime = executorService.getClock().millis(); + ackDeadlineRenewer.add(SUBSCRIPTION1, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + firstLatch.await(); + assertTrue(firstRenewal.get() < (addTime + MIN_DEADLINE_MILLIS)); + executorService.advanceTime(TIME_ADVANCE); + secondLatch.await(); + assertTrue(secondRenewal.get() < (firstRenewal.get() + MIN_DEADLINE_MILLIS)); + } + + @Test + public void testAddMessages() throws InterruptedException { + EasyMock.reset(pubsub); + final CountDownLatch firstLatch = new CountDownLatch(2); + final CountDownLatch secondLatch = new CountDownLatch(2); + final AtomicLong firstRenewalSub1 = new AtomicLong(); + final AtomicLong firstRenewalSub2 = new AtomicLong(); + final AtomicLong secondRenewalSub1 = new AtomicLong(); + final AtomicLong secondRenewalSub2 = new AtomicLong(); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub2)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID3))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub2)); + EasyMock.replay(pubsub); + long addTime1 = executorService.getClock().millis(); + ackDeadlineRenewer.add(SUBSCRIPTION1, ImmutableList.of(ACK_ID1, ACK_ID2)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + firstLatch.await(); + assertTrue(firstRenewalSub1.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + assertTrue(firstRenewalSub2.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID3); + executorService.advanceTime(TIME_ADVANCE); + secondLatch.await(); + assertTrue(secondRenewalSub1.get() < (firstRenewalSub1.get() + MIN_DEADLINE_MILLIS)); + assertTrue(secondRenewalSub2.get() < (firstRenewalSub2.get() + MIN_DEADLINE_MILLIS)); + } + + @Test + public void testAddExistingMessage() throws InterruptedException { + EasyMock.reset(pubsub); + final CountDownLatch firstLatch = new CountDownLatch(2); + final CountDownLatch secondLatch = new CountDownLatch(2); + final AtomicLong firstRenewalSub1 = new AtomicLong(); + final AtomicLong firstRenewalSub2 = new AtomicLong(); + final AtomicLong secondRenewalSub1 = new AtomicLong(); + final AtomicLong secondRenewalSub2 = new AtomicLong(); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub2)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub2)); + EasyMock.replay(pubsub); + long addTime1 = executorService.getClock().millis(); + ackDeadlineRenewer.add(SUBSCRIPTION1, ImmutableList.of(ACK_ID1, ACK_ID2)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + firstLatch.await(); + assertTrue(firstRenewalSub1.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + assertTrue(firstRenewalSub2.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + secondLatch.await(); + assertTrue(secondRenewalSub1.get() < (firstRenewalSub1.get() + MIN_DEADLINE_MILLIS)); + assertTrue(secondRenewalSub2.get() < (firstRenewalSub2.get() + MIN_DEADLINE_MILLIS)); + } + + @Test + public void testRemoveNonExistingMessage() throws InterruptedException { + EasyMock.reset(pubsub); + final CountDownLatch firstLatch = new CountDownLatch(2); + final CountDownLatch secondLatch = new CountDownLatch(2); + final AtomicLong firstRenewalSub1 = new AtomicLong(); + final AtomicLong firstRenewalSub2 = new AtomicLong(); + final AtomicLong secondRenewalSub1 = new AtomicLong(); + final AtomicLong secondRenewalSub2 = new AtomicLong(); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub2)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub2)); + EasyMock.replay(pubsub); + long addTime1 = executorService.getClock().millis(); + ackDeadlineRenewer.add(SUBSCRIPTION1, ImmutableList.of(ACK_ID1, ACK_ID2)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + firstLatch.await(); + assertTrue(firstRenewalSub1.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + assertTrue(firstRenewalSub2.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + ackDeadlineRenewer.remove(SUBSCRIPTION1, ACK_ID3); + executorService.advanceTime(TIME_ADVANCE); + secondLatch.await(); + assertTrue(secondRenewalSub1.get() < (firstRenewalSub1.get() + MIN_DEADLINE_MILLIS)); + assertTrue(secondRenewalSub2.get() < (firstRenewalSub2.get() + MIN_DEADLINE_MILLIS)); + } + + @Test + public void testRemoveMessage() throws InterruptedException { + EasyMock.reset(pubsub); + final CountDownLatch firstLatch = new CountDownLatch(2); + final CountDownLatch secondLatch = new CountDownLatch(2); + final AtomicLong firstRenewalSub1 = new AtomicLong(); + final AtomicLong firstRenewalSub2 = new AtomicLong(); + final AtomicLong secondRenewalSub1 = new AtomicLong(); + final AtomicLong secondRenewalSub2 = new AtomicLong(); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1, ACK_ID2))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(firstLatch, firstRenewalSub2)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION1, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub1)); + EasyMock.expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION2, MIN_DEADLINE_MILLIS, + TimeUnit.MILLISECONDS, ImmutableList.of(ACK_ID1))) + .andAnswer(createAnswer(secondLatch, secondRenewalSub2)); + EasyMock.replay(pubsub); + long addTime1 = executorService.getClock().millis(); + ackDeadlineRenewer.add(SUBSCRIPTION1, ImmutableList.of(ACK_ID1, ACK_ID2)); + ackDeadlineRenewer.add(SUBSCRIPTION2, ACK_ID1); + executorService.advanceTime(TIME_ADVANCE); + firstLatch.await(); + assertTrue(firstRenewalSub1.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + assertTrue(firstRenewalSub2.get() < (addTime1 + MIN_DEADLINE_MILLIS)); + ackDeadlineRenewer.remove(SUBSCRIPTION1, ACK_ID2); + executorService.advanceTime(TIME_ADVANCE); + secondLatch.await(); + assertTrue(secondRenewalSub1.get() < (firstRenewalSub1.get() + MIN_DEADLINE_MILLIS)); + assertTrue(secondRenewalSub2.get() < (firstRenewalSub2.get() + MIN_DEADLINE_MILLIS)); + } + + @Test + @SuppressWarnings("unchecked") + public void testClose() throws Exception { + PubSub pubsub = EasyMock.createStrictMock(PubSub.class); + ScheduledExecutorService executor = EasyMock.createStrictMock(ScheduledExecutorService.class); + ExecutorFactory executorFactory = EasyMock.createStrictMock(ExecutorFactory.class); + EasyMock.expect(executorFactory.get()).andReturn(executor); + PubSubOptions options = PubSubOptions.newBuilder() + .setProjectId("projectId") + .setExecutorFactory(executorFactory) + .build(); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + executorFactory.release(executor); + EasyMock.expectLastCall(); + EasyMock.replay(executor, executorFactory, pubsub); + AckDeadlineRenewer ackDeadlineRenewer = new AckDeadlineRenewer(pubsub); + ackDeadlineRenewer.close(); + EasyMock.verify(pubsub, executor, executorFactory); + } + + @Test + @SuppressWarnings("unchecked") + public void testCloseWithMessage() throws Exception { + PubSub pubsub = EasyMock.createStrictMock(PubSub.class); + ScheduledExecutorService executor = EasyMock.createStrictMock(ScheduledExecutorService.class); + ExecutorFactory executorFactory = EasyMock.createStrictMock(ExecutorFactory.class); + EasyMock.expect(executorFactory.get()).andReturn(executor); + ScheduledFuture future = EasyMock.createStrictMock(ScheduledFuture.class); + EasyMock.expect(executor.schedule(EasyMock.anyObject(), EasyMock.anyLong(), + EasyMock.eq(TimeUnit.MILLISECONDS))).andReturn(future); + PubSubOptions options = PubSubOptions.newBuilder() + .setProjectId("projectId") + .setExecutorFactory(executorFactory) + .build(); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(future.cancel(true)).andReturn(true); + executorFactory.release(executor); + EasyMock.expectLastCall(); + EasyMock.replay(executor, executorFactory, future, pubsub); + AckDeadlineRenewer ackDeadlineRenewer = new AckDeadlineRenewer(pubsub); + ackDeadlineRenewer.add(SUBSCRIPTION1, ACK_ID1); + ackDeadlineRenewer.close(); + EasyMock.verify(pubsub, executor, executorFactory, future); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/BaseSystemTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/BaseSystemTest.java new file mode 100644 index 000000000000..d9524af5925e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/BaseSystemTest.java @@ -0,0 +1,837 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.AsyncPage; +import com.google.cloud.Page; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * A base class for system tests. This class can be extended to run system tests in different + * environments (e.g. local emulator or remote Pub/Sub service). + */ +public abstract class BaseSystemTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Returns the Pub/Sub service used to issue requests. This service can be such that it interacts + * with the remote Pub/Sub service (for integration tests) or with an emulator + * (for local testing). + */ + protected abstract PubSub pubsub(); + + /** + * Formats a resource name for testing purpose. For instance, for tests against the remote + * service, it is recommended to append to the name a random or time-based seed to prevent + * name clashes. + */ + protected abstract String formatForTest(String resourceName); + + @Test + public void testCreateGetAndDeleteTopic() { + String name = formatForTest("test-create-get-delete-topic"); + Topic topic = pubsub().create(TopicInfo.of(name)); + assertEquals(name, topic.getName()); + Topic remoteTopic = pubsub().getTopic(name); + assertEquals(topic, remoteTopic); + assertTrue(topic.delete()); + } + + @Test + public void testGetTopic_NotExist() { + String name = formatForTest("test-get-non-existing-topic"); + assertNull(pubsub().getTopic(name)); + } + + @Test + public void testDeleteTopic_NotExist() { + assertFalse(pubsub().deleteTopic(formatForTest("test-delete-non-existing-topic"))); + } + + @Test + public void testCreateGetAndDeleteTopicAsync() throws ExecutionException, InterruptedException { + String name = formatForTest("test-create-get-delete-async-topic"); + Future topicFuture = pubsub().createAsync(TopicInfo.of(name)); + Topic createdTopic = topicFuture.get(); + assertEquals(name, createdTopic.getName()); + topicFuture = pubsub().getTopicAsync(name); + assertEquals(createdTopic, topicFuture.get()); + assertTrue(createdTopic.deleteAsync().get()); + } + + @Test + public void testListTopics() { + Topic topic1 = pubsub().create(TopicInfo.of(formatForTest("test-list-topic1"))); + Topic topic2 = pubsub().create(TopicInfo.of(formatForTest("test-list-topic2"))); + Topic topic3 = pubsub().create(TopicInfo.of(formatForTest("test-list-topic3"))); + Set topicNames = Sets.newHashSet(); + // We use 1 as page size to force pagination + Page topics = pubsub().listTopics(PubSub.ListOption.pageSize(1)); + Iterator iterator = topics.iterateAll(); + while (iterator.hasNext()) { + topicNames.add(iterator.next().getName()); + } + assertTrue(topicNames.contains(topic1.getName())); + assertTrue(topicNames.contains(topic2.getName())); + assertTrue(topicNames.contains(topic3.getName())); + assertTrue(topic1.delete()); + assertTrue(topic2.delete()); + assertTrue(topic3.delete()); + } + + @Test + public void testListTopicsAsync() throws ExecutionException, InterruptedException { + Topic topic1 = pubsub().create(TopicInfo.of(formatForTest("test-list-async-topic1"))); + Topic topic2 = pubsub().create(TopicInfo.of(formatForTest("test-list-async-topic2"))); + Topic topic3 = pubsub().create(TopicInfo.of(formatForTest("test-list-async-topic3"))); + Set topicNames = Sets.newHashSet(); + Future> pageFuture = pubsub().listTopicsAsync(PubSub.ListOption.pageSize(1)); + Iterator iterator = pageFuture.get().iterateAll(); + while (iterator.hasNext()) { + topicNames.add(iterator.next().getName()); + } + assertTrue(topicNames.contains(topic1.getName())); + assertTrue(topicNames.contains(topic2.getName())); + assertTrue(topicNames.contains(topic3.getName())); + assertTrue(topic1.delete()); + assertTrue(topic2.delete()); + assertTrue(topic3.delete()); + } + + @Test + public void testPublishOneMessage() { + String topic = formatForTest("test-publish-one-message-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message = Message.of("payload"); + assertNotNull(pubsub().publish(topic, message)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPublishNonExistingTopic() { + String topic = formatForTest("test-publish-non-existing-topic"); + Message message = Message.of("payload"); + thrown.expect(PubSubException.class); + pubsub().publish(topic, message); + } + + @Test + public void testPublishOneMessageAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-publish-one-message-async-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message = Message.of("payload"); + Future publishFuture = pubsub().publishAsync(topic, message); + assertNotNull(publishFuture.get()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPublishMoreMessages() { + String topic = formatForTest("test-publish-more-messages-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub().publish(topic, message1, message2); + assertEquals(2, messageIds.size()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPublishMoreMessagesAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-publish-more-messages-topic-async-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Future> publishFuture = pubsub().publishAsync(topic, message1, message2); + assertEquals(2, publishFuture.get().size()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPublishMessageList() { + String topic = formatForTest("test-publish-message-list-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPublishMessagesListAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-publish-message-list-async-topic"); + pubsub().create(TopicInfo.of(topic)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Future> publishFuture = + pubsub().publishAsync(topic, ImmutableList.of(message1, message2)); + assertEquals(2, publishFuture.get().size()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testCreateGetAndDeleteSubscription() { + String topic = formatForTest("test-create-get-delete-subscription-topic"); + pubsub().create(TopicInfo.of(topic)); + String name = formatForTest("test-create-get-delete-subscription"); + Subscription subscription = pubsub().create(SubscriptionInfo.of(topic, name)); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), subscription.getTopic()); + assertEquals(name, subscription.getName()); + assertNull(subscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, subscription.ackDeadlineSeconds()); + Subscription remoteSubscription = pubsub().getSubscription(name); + assertEquals(subscription, remoteSubscription); + assertTrue(subscription.delete()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testGetSubscription_NotExist() { + assertNull(pubsub().getSubscription(formatForTest("test-get-non-existing-subscription"))); + } + + @Test + public void testDeleteSubscription_NotExist() { + assertFalse( + pubsub().deleteSubscription(formatForTest("test-delete-non-existing-subscription"))); + } + + @Test + public void testCreateGetAndDeleteSubscriptionAsync() + throws ExecutionException, InterruptedException { + String topic = formatForTest("test-create-get-delete-async-subscription-topic"); + pubsub().create(TopicInfo.of(topic)); + String name = formatForTest("test-create-get-delete-async-subscription"); + String endpoint = "https://" + pubsub().getOptions().getProjectId() + ".appspot.com/push"; + PushConfig pushConfig = PushConfig.of(endpoint); + Future subscriptionFuture = pubsub().createAsync( + SubscriptionInfo.newBuilder(topic, name).setPushConfig(pushConfig).build()); + Subscription subscription = subscriptionFuture.get(); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), subscription.getTopic()); + assertEquals(name, subscription.getName()); + assertEquals(pushConfig, subscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, subscription.ackDeadlineSeconds()); + subscriptionFuture = pubsub().getSubscriptionAsync(name); + Subscription remoteSubscription = subscriptionFuture.get(); + assertEquals(subscription, remoteSubscription); + assertTrue(subscription.deleteAsync().get()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + @Ignore("Emulator incosistency; see issue ##988") + public void testGetSubscriptionDeletedTopic() { + String topic = formatForTest("test-get-deleted-topic-subscription-topic"); + pubsub().create(TopicInfo.of(topic)); + String name = formatForTest("test-get-deleted-topic-subscription"); + Subscription subscription = pubsub().create(SubscriptionInfo.of(topic, name)); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), subscription.getTopic()); + assertEquals(name, subscription.getName()); + assertNull(subscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, subscription.ackDeadlineSeconds()); + assertTrue(pubsub().deleteTopic(topic)); + assertNull(pubsub().getTopic(topic)); + Subscription remoteSubscription = pubsub().getSubscription(name); + assertEquals(TopicId.of("_deleted-topic_"), remoteSubscription.getTopic()); + assertEquals(name, remoteSubscription.getName()); + assertNull(remoteSubscription.getPushConfig()); + assertTrue(subscription.delete()); + } + + @Test + public void testReplaceSubscriptionPushConfig() { + String topic = formatForTest("test-replace-push-config-topic"); + pubsub().create(TopicInfo.of(topic)); + String name = formatForTest("test-replace-push-config-subscription"); + String endpoint = "https://" + pubsub().getOptions().getProjectId() + ".appspot.com/push"; + PushConfig pushConfig = PushConfig.of(endpoint); + Subscription subscription = + pubsub().create(SubscriptionInfo.newBuilder(topic, name).setPushConfig(pushConfig).build()); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), subscription.getTopic()); + assertEquals(name, subscription.getName()); + assertEquals(pushConfig, subscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, subscription.ackDeadlineSeconds()); + pubsub().replacePushConfig(name, null); + Subscription remoteSubscription = pubsub().getSubscription(name); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), + remoteSubscription.getTopic()); + assertEquals(name, remoteSubscription.getName()); + assertNull(remoteSubscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, remoteSubscription.ackDeadlineSeconds()); + assertTrue(subscription.delete()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testReplaceNonExistingSubscriptionPushConfig() { + String name = formatForTest("test-replace-push-config-non-existing-subscription"); + thrown.expect(PubSubException.class); + pubsub().replacePushConfig(name, null); + } + + @Test + public void testReplaceSubscriptionPushConfigAsync() + throws ExecutionException, InterruptedException { + String topic = formatForTest("test-replace-push-config-async-topic"); + pubsub().create(TopicInfo.of(topic)); + String name = formatForTest("test-replace-push-config-async-subscription"); + Future subscriptionFuture = + pubsub().createAsync(SubscriptionInfo.of(topic, name)); + Subscription subscription = subscriptionFuture.get(); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), subscription.getTopic()); + assertEquals(name, subscription.getName()); + assertNull(subscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, subscription.ackDeadlineSeconds()); + String endpoint = "https://" + pubsub().getOptions().getProjectId() + ".appspot.com/push"; + PushConfig pushConfig = PushConfig.of(endpoint); + pubsub().replacePushConfigAsync(name, pushConfig).get(); + Subscription remoteSubscription = pubsub().getSubscriptionAsync(name).get(); + assertEquals(TopicId.of(pubsub().getOptions().getProjectId(), topic), + remoteSubscription.getTopic()); + assertEquals(name, remoteSubscription.getName()); + assertEquals(pushConfig, remoteSubscription.getPushConfig()); + // todo(mziccard) seems not to work on the emulator (returns 60) - see #989 + // assertEquals(10, remoteSubscription.ackDeadlineSeconds()); + assertTrue(subscription.deleteAsync().get()); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testListSubscriptions() { + String topicName1 = formatForTest("test-list-subscriptions-topic1"); + String topicName2 = formatForTest("test-list-subscriptions-topic2"); + Topic topic1 = pubsub().create(TopicInfo.of(topicName1)); + Topic topic2 = pubsub().create(TopicInfo.of(topicName2)); + String subscriptionName1 = formatForTest("test-list-subscriptions-subscription1"); + String subscriptionName2 = formatForTest("test-list-subscriptions-subscription2"); + String subscriptionName3 = formatForTest("test-list-subscriptions-subscription3"); + Subscription subscription1 = + pubsub().create(SubscriptionInfo.of(topicName1, subscriptionName1)); + Subscription subscription2 = + pubsub().create(SubscriptionInfo.of(topicName1, subscriptionName2)); + Subscription subscription3 = + pubsub().create(SubscriptionInfo.of(topicName2, subscriptionName3)); + Set subscriptionNames = Sets.newHashSet(); + // We use 1 as page size to force pagination + Page subscriptions = pubsub().listSubscriptions(PubSub.ListOption.pageSize(1)); + Iterator iterator = subscriptions.iterateAll(); + while (iterator.hasNext()) { + String name = iterator.next().getName(); + subscriptionNames.add(name); + } + assertTrue(subscriptionNames.contains(subscriptionName1)); + assertTrue(subscriptionNames.contains(subscriptionName2)); + assertTrue(subscriptionNames.contains(subscriptionName3)); + Set topicSubscriptionNames = Sets.newHashSet(); + Page topic1Subscriptions = + topic1.listSubscriptions(PubSub.ListOption.pageSize(1)); + Iterator firstStringPageIterator = topic1Subscriptions.getValues().iterator(); + topicSubscriptionNames.add(firstStringPageIterator.next().getSubscription()); + assertFalse(firstStringPageIterator.hasNext()); + Iterator topicSubscriptionsIterator = + topic1Subscriptions.getNextPage().iterateAll(); + while (topicSubscriptionsIterator.hasNext()) { + topicSubscriptionNames.add(topicSubscriptionsIterator.next().getSubscription()); + } + assertEquals(2, topicSubscriptionNames.size()); + assertTrue(topicSubscriptionNames.contains(subscriptionName1)); + assertTrue(topicSubscriptionNames.contains(subscriptionName2)); + assertTrue(topic1.delete()); + assertTrue(topic2.delete()); + assertTrue(subscription1.delete()); + assertTrue(subscription2.delete()); + assertTrue(subscription3.delete()); + } + + @Test + public void testListSubscriptionsAsync() throws ExecutionException, InterruptedException { + String topicName1 = formatForTest("test-list-subscriptions-async-topic1"); + String topicName2 = formatForTest("test-list-subscriptions-async-topic2"); + Topic topic1 = pubsub().create(TopicInfo.of(topicName1)); + Topic topic2 = pubsub().create(TopicInfo.of(topicName2)); + String subscriptionName1 = formatForTest("test-list-subscriptions-async-subscription1"); + String subscriptionName2 = formatForTest("test-list-subscriptions-async-subscription2"); + String subscriptionName3 = formatForTest("test-list-subscriptions-async-subscription3"); + Subscription subscription1 = + pubsub().create(SubscriptionInfo.of(topicName1, subscriptionName1)); + Subscription subscription2 = + pubsub().create(SubscriptionInfo.of(topicName1, subscriptionName2)); + Subscription subscription3 = + pubsub().create(SubscriptionInfo.of(topicName2, subscriptionName3)); + // We use 1 as page size to force pagination + Set subscriptionNames = Sets.newHashSet(); + Future> pageFuture = + pubsub().listSubscriptionsAsync(PubSub.ListOption.pageSize(1)); + Iterator iterator = pageFuture.get().iterateAll(); + while (iterator.hasNext()) { + subscriptionNames.add(iterator.next().getName()); + } + assertTrue(subscriptionNames.contains(subscriptionName1)); + assertTrue(subscriptionNames.contains(subscriptionName2)); + assertTrue(subscriptionNames.contains(subscriptionName3)); + Set topicSubscriptionNames = Sets.newHashSet(); + AsyncPage topic1Subscriptions = + topic1.listSubscriptionsAsync(PubSub.ListOption.pageSize(1)).get(); + Iterator firstStringPageIterator = topic1Subscriptions.getValues().iterator(); + topicSubscriptionNames.add(firstStringPageIterator.next().getSubscription()); + assertFalse(firstStringPageIterator.hasNext()); + Iterator topicSubscriptionsIterator = + topic1Subscriptions.getNextPageAsync().get().iterateAll(); + while (topicSubscriptionsIterator.hasNext()) { + topicSubscriptionNames.add(topicSubscriptionsIterator.next().getSubscription()); + } + assertEquals(2, topicSubscriptionNames.size()); + assertTrue(topicSubscriptionNames.contains(subscriptionName1)); + assertTrue(topicSubscriptionNames.contains(subscriptionName2)); + assertTrue(topic1.delete()); + assertTrue(topic2.delete()); + assertTrue(subscription1.delete()); + assertTrue(subscription2.delete()); + assertTrue(subscription3.delete()); + } + + @Test + public void testPullMessages() { + String topic = formatForTest("test-pull-messages-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-pull-messages-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + Iterator iterator = pubsub().pull(subscription, 2); + assertEquals(message1.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertEquals(message2.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPullMessagesAndAutoRenewDeadline() throws InterruptedException { + String topic = formatForTest("test-pull-messages-and-renew-deadline-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-pull-messages-and-renew-deadline-subscription"); + pubsub().create( + SubscriptionInfo.newBuilder(topic, subscription).setAckDeadLineSeconds(10).build()); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + // todo(mziccard): use batch publish if #1017 gets fixed, or remove this comment + pubsub().publish(topic, message1); + pubsub().publish(topic, message2); + Iterator iterator = pubsub().pull(subscription, 2); + while (!iterator.hasNext()) { + Thread.sleep(500); + iterator = pubsub().pull(subscription, 2); + } + ReceivedMessage consumedMessage = iterator.next(); + if (!iterator.hasNext()) { + iterator = pubsub().pull(subscription, 1); + while (!iterator.hasNext()) { + Thread.sleep(500); + iterator = pubsub().pull(subscription, 1); + } + } + Thread.sleep(15000); + // first message was consumed while second message is still being renewed + Iterator nextIterator = pubsub().pull(subscription, 2); + assertTrue(nextIterator.hasNext()); + ReceivedMessage message = nextIterator.next(); + assertEquals(consumedMessage.getPayloadAsString(), message.getPayloadAsString()); + assertFalse(nextIterator.hasNext()); + consumedMessage.ack(); + iterator.next().ack(); + nextIterator = pubsub().pull(subscription, 2); + assertFalse(nextIterator.hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPullMessagesAndModifyAckDeadline() throws InterruptedException { + String topic = formatForTest("test-pull-messages-and-modify-deadline-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-pull-messages-and-modify-deadline-subscription"); + pubsub().create( + SubscriptionInfo.newBuilder(topic, subscription).setAckDeadLineSeconds(10).build()); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + // todo(mziccard): use batch publish if #1017 gets fixed, or remove this comment + pubsub().publish(topic, message1); + pubsub().publish(topic, message2); + // Consume all messages and stop ack renewal + List receivedMessages = Lists.newArrayList(pubsub().pull(subscription, 2)); + while (receivedMessages.size() < 2) { + Thread.sleep(500); + Iterators.addAll(receivedMessages, pubsub().pull(subscription, 2)); + } + receivedMessages.get(0).modifyAckDeadline(60, TimeUnit.SECONDS); + Thread.sleep(15000); + // first message was renewed while second message should still be sent on pull requests + Iterator nextIterator = pubsub().pull(subscription, 2); + assertTrue(nextIterator.hasNext()); + ReceivedMessage message = nextIterator.next(); + assertEquals(receivedMessages.get(1).getPayloadAsString(), message.getPayloadAsString()); + assertFalse(nextIterator.hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPullNonExistingSubscription() { + thrown.expect(PubSubException.class); + pubsub().pull(formatForTest("non-existing-subscription"), 2); + } + + @Test + public void testPullMessagesAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-pull-messages-async-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-pull-messages-async-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + Iterator iterator = pubsub().pullAsync(subscription, 2).get(); + assertEquals(message1.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertEquals(message2.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPullMessagesAsyncNonImmediately() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-pull-messages-async-non-immediately-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-pull-messages-async-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Future> future = pubsub().pullAsync(subscription, 2); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + Iterator iterator = future.get(); + assertEquals(message1.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertEquals(message2.getPayloadAsString(), iterator.next().getPayloadAsString()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testPullAsyncNonExistingSubscription() + throws ExecutionException, InterruptedException { + thrown.expect(ExecutionException.class); + pubsub().pullAsync(formatForTest("non-existing-subscription"), 2).get(); + } + + @Test + public void testMessageConsumer() throws Exception { + String topic = formatForTest("test-message-consumer-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-message-consumer-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Set payloads = Sets.newHashSet("payload1", "payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + final List receivedMessages = Collections.synchronizedList(new ArrayList()); + final CountDownLatch countDownLatch = new CountDownLatch(2); + MessageProcessor processor = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + receivedMessages.add(message); + countDownLatch.countDown(); + } + }; + try(MessageConsumer consumer = pubsub().pullAsync(subscription, processor)) { + countDownLatch.await(); + } + for (Message message : receivedMessages) { + payloads.contains(message.getPayloadAsString()); + } + // Messages have all been acked, they should not be pulled again + Iterator messages = pubsub().pull(subscription, 2); + assertFalse(messages.hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testMessageConsumerNack() throws Exception { + String topic = formatForTest("test-message-consumer-nack-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-message-consumer-nack-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Set payloads = Sets.newHashSet("payload1", "payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + final List receivedMessages = Collections.synchronizedList(new ArrayList()); + final CountDownLatch countDownLatch = new CountDownLatch(2); + MessageProcessor processor = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + receivedMessages.add(message); + countDownLatch.countDown(); + throw new RuntimeException("Force nack"); + } + }; + try (MessageConsumer consumer = pubsub().pullAsync(subscription, processor)) { + countDownLatch.await(); + } + for (Message message : receivedMessages) { + payloads.contains(message.getPayloadAsString()); + } + // Messages have all been nacked, we should be able to pull them again + Thread.sleep(5000); + Iterator messages = pubsub().pull(subscription, 2); + while (messages.hasNext()) { + payloads.contains(messages.next().getPayloadAsString()); + } + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testMessageConsumerWithMoreMessages() throws Exception { + String topic = formatForTest("test-message-consumer-more-messages-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-message-consumer-more-messages-subscriptions"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + int totalMessages = 200; + Set payloads = Sets.newHashSetWithExpectedSize(totalMessages); + List messagesToSend = Lists.newArrayListWithCapacity(totalMessages); + for (int i = 0; i < totalMessages; i++) { + String payload = "payload" + i; + messagesToSend.add(Message.of(payload)); + payloads.add(payload); + + } + List messageIds = pubsub().publish(topic, messagesToSend); + assertEquals(totalMessages, messageIds.size()); + final List receivedMessages = Collections.synchronizedList(new ArrayList()); + final CountDownLatch countDownLatch = new CountDownLatch(totalMessages); + MessageProcessor processor = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + receivedMessages.add(message); + countDownLatch.countDown(); + } + }; + try(MessageConsumer consumer = pubsub().pullAsync(subscription, processor)) { + countDownLatch.await(); + } + // Messages have all been acked, they should not be pulled again + Iterator messages = pubsub().pull(subscription, totalMessages); + assertFalse(messages.hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testMessageConsumerAndAutoRenewDeadline() throws Exception { + String topic = formatForTest("test-message-consumer-and-renew-deadline-topic"); + pubsub().create(TopicInfo.of(topic)); + final String subscription = + formatForTest("test-message-consumer-and-renew-deadline-subscription"); + pubsub().create( + SubscriptionInfo.newBuilder(topic, subscription).setAckDeadLineSeconds(10).build()); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + Set payloads = Sets.newHashSet("payload1", "payload2"); + List messageIds = pubsub().publish(topic, ImmutableList.of(message1, message2)); + assertEquals(2, messageIds.size()); + final List receivedMessages = Collections.synchronizedList(new ArrayList()); + final CountDownLatch countDownLatch = new CountDownLatch(2); + MessageProcessor processor = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + receivedMessages.add(message); + Thread.sleep(15000); + // message deadline is being renewed, it should not be pulled again + Iterator messages = pubsub().pull(subscription, 2); + assertFalse(messages.hasNext()); + countDownLatch.countDown(); + } + }; + try(MessageConsumer consumer = pubsub().pullAsync(subscription, processor)) { + countDownLatch.await(); + } + for (Message message : receivedMessages) { + payloads.contains(message.getPayloadAsString()); + } + // Messages have all been acked, they should not be pulled again + Iterator messages = pubsub().pull(subscription, 2); + assertFalse(messages.hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackOneMessage() { + String topic = formatForTest("test-ack-one-message-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-one-message-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message = Message.of("payload"); + assertNotNull(pubsub().publish(topic, message)); + Iterator receivedMessages = pubsub().pull(subscription, 1); + receivedMessages.next().nack(); + receivedMessages = pubsub().pull(subscription, 1); + receivedMessages.next().ack(); + assertFalse(pubsub().pull(subscription, 1).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackOneMessageAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-ack-one-message-async-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-one-message-async-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message = Message.of("payload"); + assertNotNull(pubsub().publish(topic, message)); + Iterator receivedMessages = pubsub().pull(subscription, 1); + receivedMessages.next().nackAsync().get(); + receivedMessages = pubsub().pull(subscription, 1); + receivedMessages.next().ackAsync().get(); + assertFalse(pubsub().pull(subscription, 1).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackMoreMessages() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-ack-more-messages-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-more-messages-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + assertNotNull(pubsub().publish(topic, message1, message2)); + Iterator receivedMessages = pubsub().pull(subscription, 2); + pubsub().nack(subscription, receivedMessages.next().getAckId(), + receivedMessages.next().getAckId()); + receivedMessages = pubsub().pull(subscription, 2); + pubsub().ack(subscription, receivedMessages.next().getAckId(), + receivedMessages.next().getAckId()); + assertFalse(pubsub().pull(subscription, 2).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackMoreMessagesAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-ack-more-messages-async-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-more-messages-async-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + assertNotNull(pubsub().publish(topic, message1, message2)); + Iterator receivedMessages = pubsub().pull(subscription, 2); + pubsub().nackAsync(subscription, receivedMessages.next().getAckId(), + receivedMessages.next().getAckId()) + .get(); + receivedMessages = pubsub().pull(subscription, 2); + pubsub().ackAsync(subscription, receivedMessages.next().getAckId(), + receivedMessages.next().getAckId()) + .get(); + assertFalse(pubsub().pull(subscription, 2).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackMessageList() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-ack-message-list-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-message-list-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + assertNotNull(pubsub().publish(topic, ImmutableList.of(message1, message2))); + Iterator receivedMessages = pubsub().pull(subscription, 2); + pubsub().nack(subscription, + ImmutableList.of(receivedMessages.next().getAckId(), receivedMessages.next().getAckId())); + receivedMessages = pubsub().pull(subscription, 2); + pubsub().ack(subscription, + ImmutableList.of(receivedMessages.next().getAckId(), receivedMessages.next().getAckId())); + assertFalse(pubsub().pull(subscription, 2).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } + + @Test + public void testAckAndNackMessageListAsync() throws ExecutionException, InterruptedException { + String topic = formatForTest("test-ack-message-list-async-topic"); + pubsub().create(TopicInfo.of(topic)); + String subscription = formatForTest("test-ack-message-list-async-subscription"); + pubsub().create(SubscriptionInfo.of(topic, subscription)); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + assertNotNull(pubsub().publish(topic, ImmutableList.of(message1, message2))); + Iterator receivedMessages = pubsub().pull(subscription, 2); + pubsub().nackAsync(subscription, ImmutableList.of(receivedMessages.next().getAckId(), + receivedMessages.next().getAckId())).get(); + receivedMessages = pubsub().pull(subscription, 2); + pubsub().ackAsync(subscription, ImmutableList.of(receivedMessages.next().getAckId(), + receivedMessages.next().getAckId())).get(); + assertFalse(pubsub().pull(subscription, 2).hasNext()); + assertTrue(pubsub().deleteSubscription(subscription)); + assertTrue(pubsub().deleteTopic(topic)); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/LocalSystemTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/LocalSystemTest.java new file mode 100644 index 000000000000..aba64436933b --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/LocalSystemTest.java @@ -0,0 +1,55 @@ +/* + * 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.pubsub; + +import com.google.cloud.pubsub.testing.LocalPubSubHelper; + +import org.joda.time.Duration; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; + +public class LocalSystemTest extends BaseSystemTest { + + private static LocalPubSubHelper pubsubHelper; + private static PubSub pubsub; + + @Override + protected PubSub pubsub() { + return pubsub; + } + + @Override + protected String formatForTest(String resourceName) { + return resourceName; + } + + @BeforeClass + public static void startServer() throws IOException, InterruptedException { + pubsubHelper = LocalPubSubHelper.create(); + pubsubHelper.start(); + pubsub = pubsubHelper.getOptions().getService(); + } + + @AfterClass + public static void stopServer() throws Exception { + pubsub.close(); + pubsubHelper.reset(); + pubsubHelper.stop(Duration.standardMinutes(3)); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageConsumerImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageConsumerImplTest.java new file mode 100644 index 000000000000..c0acf8199ce0 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageConsumerImplTest.java @@ -0,0 +1,453 @@ +/* + * 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.pubsub; + +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.spi.PubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpc.PullCallback; +import com.google.cloud.pubsub.spi.PubSubRpc.PullFuture; +import com.google.common.util.concurrent.ForwardingListenableFuture; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; + +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public class MessageConsumerImplTest { + + private static final String PROJECT = "project"; + private static final String SUBSCRIPTION = "subscription"; + private static final String SUBSCRIPTION_PB = "projects/project/subscriptions/subscription"; + private static final int MAX_QUEUED_CALLBACKS = 42; + private static final Message MESSAGE1 = Message.of("payload1"); + private static final Message MESSAGE2 = Message.of("payload2"); + private static final String ACK_ID1 = "ack-id1"; + private static final String ACK_ID2 = "ack-id2"; + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE1_PB = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setAckId(ACK_ID1) + .setMessage(MESSAGE1.toPb()) + .build(); + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE2_PB = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setAckId(ACK_ID2) + .setMessage(MESSAGE2.toPb()) + .build(); + private static final PullResponse PULL_RESPONSE = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE1_PB) + .addReceivedMessages(MESSAGE2_PB) + .build(); + private static final MessageProcessor DO_NOTHING_PROCESSOR = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + // do nothing + } + }; + private static final MessageProcessor THROW_PROCESSOR = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + throw new RuntimeException(); + } + }; + private static final PullResponse EMPTY_RESPONSE = PullResponse.getDefaultInstance(); + + private PubSubRpc pubsubRpc; + private PubSub pubsub; + private PubSubOptions options; + private AckDeadlineRenewer renewer; + + @Rule + public Timeout globalTimeout = Timeout.seconds(60); + + static final class TestPullFuture + extends ForwardingListenableFuture.SimpleForwardingListenableFuture + implements PullFuture { + + TestPullFuture(PullResponse response) { + super(Futures.immediateFuture(response)); + } + + @Override + public void addCallback(final PullCallback callback) { + Futures.addCallback(delegate(), new FutureCallback() { + @Override + public void onSuccess(PullResponse result) { + callback.success(result); + } + + @Override + public void onFailure(Throwable error) { + callback.failure(error); + } + }); + } + } + + @Before + public void setUp() { + pubsubRpc = EasyMock.createStrictMock(PubSubRpc.class); + pubsub = EasyMock.createMock(PubSub.class); + options = EasyMock.createStrictMock(PubSubOptions.class); + renewer = EasyMock.createMock(AckDeadlineRenewer.class); + } + + @After + public void tearDown() { + EasyMock.verify(pubsubRpc); + EasyMock.verify(pubsub); + EasyMock.verify(options); + EasyMock.verify(renewer); + + } + + private static PullRequest pullRequest(int maxQueuedCallbacks) { + return PullRequest.newBuilder() + .setMaxMessages(maxQueuedCallbacks) + .setSubscription(SUBSCRIPTION_PB) + .setReturnImmediately(false) + .build(); + } + + private static IAnswer createAnswer(final CountDownLatch latch) { + return new IAnswer() { + @Override + public Void answer() throws Throwable { + latch.countDown(); + return null; + } + }; + } + + @Test + public void testMessageConsumerAck() throws Exception { + PullRequest request = pullRequest(MAX_QUEUED_CALLBACKS); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(pubsub.getOptions()).andReturn(options).times(2); + final CountDownLatch latch = new CountDownLatch(2); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID2)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request)).andReturn(new TestPullFuture(PULL_RESPONSE)); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, DO_NOTHING_PROCESSOR) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS) + .build()) { + latch.await(); + } + } + + @Test + public void testMessageConsumerNack() throws Exception { + PullRequest request = pullRequest(MAX_QUEUED_CALLBACKS); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(pubsub.getOptions()).andReturn(options).times(2); + final CountDownLatch latch = new CountDownLatch(2); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID2)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request)).andReturn(new TestPullFuture(PULL_RESPONSE)); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, THROW_PROCESSOR) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS) + .build()) { + latch.await(); + } + } + + @Test + public void testMessageConsumerMultipleCallsAck() throws Exception { + PullRequest request1 = pullRequest(MAX_QUEUED_CALLBACKS); + PullRequest request2 = pullRequest(MAX_QUEUED_CALLBACKS - 1); + PullResponse response1 = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE1_PB) + .build(); + final PullResponse response2 = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE2_PB) + .build(); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + final CountDownLatch nextPullLatch = new CountDownLatch(1); + final CountDownLatch latch = new CountDownLatch(2); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID1)).andAnswer(new IAnswer>() { + @Override + public Future answer() throws Throwable { + nextPullLatch.await(); + return null; + } + }); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID2)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request1)).andReturn(new TestPullFuture(response1)); + EasyMock.expect(pubsubRpc.pull(request2)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + nextPullLatch.countDown(); + return new TestPullFuture(response2); + } + }); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, DO_NOTHING_PROCESSOR) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS) + .build()) { + latch.await(); + } + } + + @Test + public void testMessageConsumerMultipleCallsNack() throws Exception { + PullRequest request1 = pullRequest(MAX_QUEUED_CALLBACKS); + PullRequest request2 = pullRequest(MAX_QUEUED_CALLBACKS - 1); + PullResponse response1 = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE1_PB) + .build(); + final PullResponse response2 = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE2_PB) + .build(); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + final CountDownLatch nextPullLatch = new CountDownLatch(1); + final CountDownLatch latch = new CountDownLatch(2); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID1)).andAnswer(new IAnswer>() { + @Override + public Future answer() throws Throwable { + nextPullLatch.await(); + return null; + } + }); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID2)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request1)).andReturn(new TestPullFuture(response1)); + EasyMock.expect(pubsubRpc.pull(request2)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + nextPullLatch.countDown(); + return new TestPullFuture(response2); + } + }); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, THROW_PROCESSOR) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS) + .build()) { + latch.await(); + } + } + + @Test + public void testMessageConsumerMaxCallbacksAck() throws Exception { + PullRequest request1 = pullRequest(2); + PullRequest request2 = pullRequest(1); + final PullResponse otherPullResponse = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE1_PB) + .build(); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(pubsub.getOptions()).andReturn(options).times(2); + final CountDownLatch nextPullLatch = new CountDownLatch(1); + final CountDownLatch latch = new CountDownLatch(3); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID2)).andAnswer(new IAnswer>() { + @Override + public Future answer() throws Throwable { + nextPullLatch.await(); + return null; + } + }); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request1)).andReturn(new TestPullFuture(PULL_RESPONSE)); + EasyMock.expect(pubsubRpc.pull(request2)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + nextPullLatch.countDown(); + return new TestPullFuture(otherPullResponse); + } + }); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, DO_NOTHING_PROCESSOR) + .maxQueuedCallbacks(2) + .build()) { + latch.await(); + } + } + + @Test + public void testMessageConsumerMaxCallbacksNack() throws Exception { + PullRequest request1 = pullRequest(2); + PullRequest request2 = pullRequest(1); + final PullResponse otherPullResponse = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE1_PB) + .build(); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(pubsub.getOptions()).andReturn(options).times(2); + final CountDownLatch nextPullLatch = new CountDownLatch(1); + final CountDownLatch latch = new CountDownLatch(3); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID2)).andAnswer(new IAnswer>() { + @Override + public Future answer() throws Throwable { + nextPullLatch.await(); + return null; + } + }); + EasyMock.expect(pubsub.getOptions()).andReturn(options); + EasyMock.expect(pubsub.nackAsync(SUBSCRIPTION, ACK_ID1)).andReturn(null); + EasyMock.replay(pubsub); + EasyMock.expect(pubsubRpc.pull(request1)).andReturn(new TestPullFuture(PULL_RESPONSE)); + EasyMock.expect(pubsubRpc.pull(request2)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + nextPullLatch.countDown(); + return new TestPullFuture(otherPullResponse); + } + }); + EasyMock.expect(pubsubRpc.pull(EasyMock.anyObject())) + .andReturn(new TestPullFuture(EMPTY_RESPONSE)).anyTimes(); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.add(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.remove(SUBSCRIPTION, ACK_ID2); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + renewer.add(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall(); + renewer.remove(SUBSCRIPTION, ACK_ID1); + EasyMock.expectLastCall().andAnswer(createAnswer(latch)); + EasyMock.replay(pubsubRpc, options, renewer); + try (MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, THROW_PROCESSOR) + .maxQueuedCallbacks(2) + .build()) { + latch.await(); + } + } + + @Test + public void testClose() throws Exception { + EasyMock.expect(options.getRpc()).andReturn(pubsubRpc); + EasyMock.expect(options.getService()).andReturn(pubsub); + final ExecutorService executor = EasyMock.createStrictMock(ExecutorService.class); + executor.shutdown(); + EasyMock.expectLastCall(); + EasyMock.replay(pubsubRpc, pubsub, options, executor, renewer); + MessageConsumer consumer = + MessageConsumerImpl.builder(options, SUBSCRIPTION, renewer, DO_NOTHING_PROCESSOR) + .maxQueuedCallbacks(MAX_QUEUED_CALLBACKS) + .executorFactory(new ExecutorFactory() { + @Override + public ExecutorService get() { + return executor; + } + + @Override + public void release(ExecutorService executor) { + executor.shutdown(); + } + }).build(); + consumer.close(); + // closing again should do nothing + consumer.close(); + EasyMock.verify(executor); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageTest.java new file mode 100644 index 000000000000..ee4fe3055a10 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/MessageTest.java @@ -0,0 +1,176 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.cloud.ByteArray; +import com.google.common.collect.ImmutableMap; + +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class MessageTest { + + private static final String MESSAGE_ID = "messageId"; + private static final String PAYLOAD_STRING = "payload"; + private static final ByteArray PAYLOAD = + ByteArray.copyFrom("payload".getBytes(StandardCharsets.UTF_8)); + private static final Map ATTRIBUTES = + ImmutableMap.of("key1", "value1", "key2", "value2"); + private static final Long PUBLISH_TIME = 42L; + private static final Message MESSAGE_STRING = Message.newBuilder(PAYLOAD_STRING) + .setId(MESSAGE_ID) + .setAttributes(ATTRIBUTES) + .setPublishTime(PUBLISH_TIME) + .build(); + private static final Message MESSAGE = Message.newBuilder(PAYLOAD) + .setId(MESSAGE_ID) + .setAttributes(ATTRIBUTES) + .setPublishTime(PUBLISH_TIME) + .build(); + private static final Message DEPRECATED_MESSAGE_STRING = Message.builder(PAYLOAD_STRING) + .setId(MESSAGE_ID) + .attributes(ATTRIBUTES) + .setPublishTime(PUBLISH_TIME) + .build(); + private static final Message DEPRECATED_MESSAGE = Message.builder(PAYLOAD) + .setId(MESSAGE_ID) + .attributes(ATTRIBUTES) + .setPublishTime(PUBLISH_TIME) + .build(); + + @Test + public void testToBuilder() { + compareMessage(MESSAGE, MESSAGE.toBuilder().build()); + Message message = MESSAGE.toBuilder() + .setPayload("newPayload") + .clearAttributes() + .addAttribute("key1", "value1") + .build(); + assertEquals("newPayload", message.getPayloadAsString()); + assertEquals(ImmutableMap.of("key1", "value1"), message.getAttributes()); + message = message.toBuilder() + .setPayload(PAYLOAD_STRING) + .removeAttribute("key1") + .setAttributes(ATTRIBUTES) + .build(); + compareMessage(MESSAGE, message); + } + + @Test + public void testBuilder() { + assertEquals(MESSAGE_ID, MESSAGE.getId()); + assertEquals(PAYLOAD, MESSAGE.getPayload()); + assertEquals(PAYLOAD_STRING, MESSAGE.getPayloadAsString()); + assertEquals(ATTRIBUTES, MESSAGE.getAttributes()); + assertEquals(PUBLISH_TIME, MESSAGE.getPublishTime()); + assertEquals(MESSAGE_ID, MESSAGE_STRING.getId()); + assertEquals(PAYLOAD, MESSAGE_STRING.getPayload()); + assertEquals(PAYLOAD_STRING, MESSAGE_STRING.getPayloadAsString()); + assertEquals(ATTRIBUTES, MESSAGE_STRING.getAttributes()); + assertEquals(PUBLISH_TIME, MESSAGE_STRING.getPublishTime()); + compareMessage(MESSAGE, MESSAGE_STRING); + Message message = Message.newBuilder(PAYLOAD) + .setId(MESSAGE_ID) + .setAttributes(ATTRIBUTES) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .setPublishTime(PUBLISH_TIME) + .build(); + assertEquals(MESSAGE_ID, message.getId()); + assertEquals(PAYLOAD, message.getPayload()); + assertEquals(PAYLOAD_STRING, message.getPayloadAsString()); + assertEquals(ATTRIBUTES, message.getAttributes()); + assertEquals(PUBLISH_TIME, message.getPublishTime()); + compareMessage(MESSAGE, message); + } + + @Test + public void testBuilderDeprecated() { + assertEquals(MESSAGE_ID, DEPRECATED_MESSAGE.id()); + assertEquals(PAYLOAD, DEPRECATED_MESSAGE.payload()); + assertEquals(PAYLOAD_STRING, DEPRECATED_MESSAGE.payloadAsString()); + assertEquals(ATTRIBUTES, DEPRECATED_MESSAGE.attributes()); + assertEquals(PUBLISH_TIME, DEPRECATED_MESSAGE.publishTime()); + assertEquals(MESSAGE_ID, DEPRECATED_MESSAGE_STRING.id()); + assertEquals(PAYLOAD, DEPRECATED_MESSAGE_STRING.payload()); + assertEquals(PAYLOAD_STRING, DEPRECATED_MESSAGE_STRING.payloadAsString()); + assertEquals(ATTRIBUTES, DEPRECATED_MESSAGE_STRING.attributes()); + assertEquals(PUBLISH_TIME, DEPRECATED_MESSAGE_STRING.publishTime()); + compareMessage(MESSAGE, DEPRECATED_MESSAGE_STRING); + Message message = Message.builder(PAYLOAD) + .setId(MESSAGE_ID) + .attributes(ATTRIBUTES) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .setPublishTime(PUBLISH_TIME) + .build(); + assertEquals(MESSAGE_ID, message.id()); + assertEquals(PAYLOAD, message.payload()); + assertEquals(PAYLOAD_STRING, message.payloadAsString()); + assertEquals(ATTRIBUTES, message.attributes()); + assertEquals(PUBLISH_TIME, message.publishTime()); + compareMessage(MESSAGE, message); + } + + @Test + public void testOf() { + Message message1 = Message.of(PAYLOAD_STRING); + assertNull(message1.getId()); + assertEquals(PAYLOAD, message1.getPayload()); + assertEquals(PAYLOAD_STRING, message1.getPayloadAsString()); + assertEquals(ImmutableMap.of(), message1.getAttributes()); + assertNull(message1.getPublishTime()); + Message message2 = Message.of(PAYLOAD); + assertNull(message2.getId()); + assertEquals(PAYLOAD, message2.getPayload()); + assertEquals(PAYLOAD_STRING, message2.getPayloadAsString()); + assertEquals(ImmutableMap.of(), message2.getAttributes()); + assertNull(message2.getPublishTime()); + compareMessage(message1 ,message2); + } + + @Test + public void testToAndFromPb() { + compareMessage(MESSAGE, Message.fromPb(MESSAGE.toPb())); + compareMessage(MESSAGE_STRING, Message.fromPb(MESSAGE_STRING.toPb())); + } + + @Test + public void testToAndFromPbIncomplete() { + Message message = Message.of(PAYLOAD_STRING); + compareMessage(message, Message.fromPb(message.toPb())); + message = Message.of(PAYLOAD); + compareMessage(message, Message.fromPb(message.toPb())); + } + + private void compareMessage(Message expected, Message value) { + assertEquals(expected, value); + assertEquals(expected.getId(), value.getId()); + assertEquals(expected.getPayload(), value.getPayload()); + assertEquals(expected.getPayloadAsString(), value.getPayloadAsString()); + assertEquals(expected.getAttributes(), value.getAttributes()); + assertEquals(expected.getPublishTime(), value.getPublishTime()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/OptionTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/OptionTest.java new file mode 100644 index 000000000000..8e4230198d2a --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/OptionTest.java @@ -0,0 +1,66 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; + +import com.google.cloud.pubsub.Option.OptionType; +import com.google.cloud.pubsub.PubSub.ListOption; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class OptionTest { + + private static final OptionType OPTION_TYPE = ListOption.OptionType.PAGE_SIZE; + private static final OptionType ANOTHER_OPTION_TYPE = ListOption.OptionType.PAGE_TOKEN; + private static final String VALUE = "some value"; + private static final String OTHER_VALUE = "another value"; + private static final Option OPTION = new Option(OPTION_TYPE, VALUE) {}; + private static final Option OPTION_EQUALS = new Option(OPTION_TYPE, VALUE) {}; + private static final Option OPTION_NOT_EQUALS1 = new Option(ANOTHER_OPTION_TYPE, OTHER_VALUE) {}; + private static final Option OPTION_NOT_EQUALS2 = new Option(ANOTHER_OPTION_TYPE, VALUE) {}; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testEquals() { + assertEquals(OPTION, OPTION_EQUALS); + assertNotEquals(OPTION, OPTION_NOT_EQUALS1); + assertNotEquals(OPTION, OPTION_NOT_EQUALS2); + } + + @Test + public void testHashCode() { + assertEquals(OPTION.hashCode(), OPTION_EQUALS.hashCode()); + } + + @Test + public void testConstructor() { + assertEquals(OPTION_TYPE, OPTION.getOptionType()); + assertEquals(VALUE, OPTION.getValue()); + Option option = new Option(OPTION_TYPE, null) {}; + assertEquals(OPTION_TYPE, option.getOptionType()); + assertNull(option.getValue()); + thrown.expect(NullPointerException.class); + new Option(null, VALUE) {}; + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubImplTest.java new file mode 100644 index 000000000000..4287fab025fe --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubImplTest.java @@ -0,0 +1,1986 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.cloud.AsyncPage; +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.Identity; +import com.google.cloud.Page; +import com.google.cloud.Policy; +import com.google.cloud.RetryParams; +import com.google.cloud.Role; +import com.google.cloud.pubsub.MessageConsumerImplTest.TestPullFuture; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PubSub.PullOption; +import com.google.cloud.pubsub.spi.PubSubRpc; +import com.google.cloud.pubsub.spi.PubSubRpc.PullCallback; +import com.google.cloud.pubsub.spi.PubSubRpc.PullFuture; +import com.google.cloud.pubsub.spi.PubSubRpcFactory; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; +import com.google.protobuf.Empty; +import com.google.pubsub.v1.AcknowledgeRequest; +import com.google.pubsub.v1.DeleteSubscriptionRequest; +import com.google.pubsub.v1.DeleteTopicRequest; +import com.google.pubsub.v1.GetSubscriptionRequest; +import com.google.pubsub.v1.GetTopicRequest; +import com.google.pubsub.v1.ListSubscriptionsRequest; +import com.google.pubsub.v1.ListSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicSubscriptionsRequest; +import com.google.pubsub.v1.ListTopicSubscriptionsResponse; +import com.google.pubsub.v1.ListTopicsRequest; +import com.google.pubsub.v1.ListTopicsResponse; +import com.google.pubsub.v1.ModifyAckDeadlineRequest; +import com.google.pubsub.v1.ModifyPushConfigRequest; +import com.google.pubsub.v1.PublishRequest; +import com.google.pubsub.v1.PublishResponse; +import com.google.pubsub.v1.PullRequest; +import com.google.pubsub.v1.PullResponse; +import com.google.pubsub.v1.SubscriptionName; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class PubSubImplTest { + + private static final String PROJECT = "project"; + private static final String PROJECT_PB = "projects/project"; + private static final String TOPIC = "topic"; + private static final String TOPIC_NAME_PB = "projects/project/topics/topic"; + private static final TopicInfo TOPIC_INFO = TopicInfo.of(TOPIC); + private static final Function TOPIC_TO_PB_FUNCTION = + new Function() { + @Override + public com.google.pubsub.v1.Topic apply(TopicInfo topicInfo) { + return topicInfo.toPb(PROJECT); + } + }; + private static final Message MESSAGE = Message.of("payload"); + private static final String SUBSCRIPTION = "subscription"; + private static final String SUBSCRIPTION_NAME_PB = "projects/project/subscriptions/subscription"; + private static final PushConfig PUSH_CONFIG = PushConfig.of("endpoint"); + private static final SubscriptionInfo SUBSCRIPTION_INFO = + SubscriptionInfo.newBuilder(TOPIC, SUBSCRIPTION) + .setAckDeadLineSeconds(42) + .setPushConfig(PUSH_CONFIG) + .build(); + private static final SubscriptionInfo COMPLETE_SUBSCRIPTION_INFO = + SubscriptionInfo.newBuilder(TopicId.of(PROJECT, TOPIC), SUBSCRIPTION) + .setAckDeadLineSeconds(42) + .setPushConfig(PUSH_CONFIG) + .build(); + private static final Message MESSAGE1 = Message.of("payload1"); + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE_PB1 = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE1.toPb()) + .setAckId("ackId1") + .build(); + private static final Message MESSAGE2 = Message.of("payload2"); + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE_PB2 = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE2.toPb()) + .setAckId("ackId2") + .build(); + private static final Policy POLICY = Policy.newBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + private static final com.google.iam.v1.Policy POLICY_PB = PolicyMarshaller.INSTANCE.toPb(POLICY); + private static final Function + SUBSCRIPTION_TO_PB_FUNCTION = + new Function() { + @Override + public com.google.pubsub.v1.Subscription apply(SubscriptionInfo subscriptionInfo) { + return subscriptionInfo.toPb(PROJECT); + } + }; + private static final Function SUBSCRIPTION_ID_TO_PB_FUNCTION = + new Function() { + @Override + public String apply(SubscriptionId subscriptionId) { + return SubscriptionName.create(subscriptionId.getProject(), + subscriptionId.getSubscription()).toString(); + } + }; + private static final MessageProcessor DO_NOTHING = new MessageProcessor() { + @Override + public void process(Message message) throws Exception { + // do nothing + } + }; + + private PubSubOptions options; + private PubSubRpcFactory rpcFactoryMock; + private PubSubRpc pubsubRpcMock; + private AckDeadlineRenewer renewerMock; + private PubSub pubsub; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + rpcFactoryMock = EasyMock.createStrictMock(PubSubRpcFactory.class); + pubsubRpcMock = EasyMock.createStrictMock(PubSubRpc.class); + renewerMock = EasyMock.createStrictMock(AckDeadlineRenewer.class); + options = EasyMock.createMock(PubSubOptions.class); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpcMock).anyTimes(); + EasyMock.expect(options.getRetryParams()).andReturn(RetryParams.noRetries()).anyTimes(); + EasyMock.replay(rpcFactoryMock, pubsubRpcMock, renewerMock, options); + EasyMock.reset(pubsubRpcMock, renewerMock); + } + + @After + public void tearDown() { + EasyMock.verify(rpcFactoryMock, pubsubRpcMock, renewerMock, options); + } + + private void resetOptionsForList(int pageCount) { + EasyMock.reset(options); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).times(pageCount); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpcMock).times(pageCount); + EasyMock.expect(options.getService()).andReturn(pubsub).times(pageCount); + EasyMock.replay(options); + } + + @Test + public void testGetOptions() { + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertSame(options, pubsub.getOptions()); + } + + @Test + public void testCreateTopic() { + com.google.pubsub.v1.Topic topicPb = TOPIC_INFO.toPb(PROJECT); + Future response = Futures.immediateFuture(topicPb); + EasyMock.expect(pubsubRpcMock.create(topicPb)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Topic topic = pubsub.create(TOPIC_INFO); + assertEquals(new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), topic); + } + + @Test + public void testCreateTopicAsync() throws ExecutionException, InterruptedException { + com.google.pubsub.v1.Topic topicPb = TOPIC_INFO.toPb(PROJECT); + Future response = Futures.immediateFuture(topicPb); + EasyMock.expect(pubsubRpcMock.create(topicPb)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Topic topic = pubsub.createAsync(TOPIC_INFO).get(); + assertEquals(new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), topic); + } + + @Test + public void testGetTopic() { + GetTopicRequest request = GetTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = + Futures.immediateFuture(TOPIC_INFO.toPb(PROJECT)); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Topic topic = pubsub.getTopic(TOPIC); + assertEquals(new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), topic); + } + + @Test + public void testGetTopic_Null() { + GetTopicRequest request = GetTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future responseFuture = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getTopic(TOPIC)); + } + + @Test + public void testGetTopicAsync() throws ExecutionException, InterruptedException { + GetTopicRequest request = GetTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = + Futures.immediateFuture(TOPIC_INFO.toPb(PROJECT)); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future topicFuture = pubsub.getTopicAsync(TOPIC); + assertEquals(new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), topicFuture.get()); + } + + @Test + public void testGetTopicAsync_Null() throws ExecutionException, InterruptedException { + GetTopicRequest request = GetTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future responseFuture = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getTopicAsync(TOPIC).get()); + } + + @Test + public void testDeleteTopic() { + DeleteTopicRequest request = DeleteTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertTrue(pubsub.deleteTopic(TOPIC)); + } + + @Test + public void testDeleteTopic_Null() { + DeleteTopicRequest request = DeleteTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertFalse(pubsub.deleteTopic(TOPIC)); + } + + @Test + public void testDeleteTopicAsync() throws ExecutionException, InterruptedException { + DeleteTopicRequest request = DeleteTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertTrue(pubsub.deleteTopicAsync(TOPIC).get()); + } + + @Test + public void testDeleteTopicAsync_Null() throws ExecutionException, InterruptedException { + DeleteTopicRequest request = DeleteTopicRequest.newBuilder().setTopic(TOPIC_NAME_PB).build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertFalse(pubsub.deleteTopicAsync(TOPIC).get()); + } + + @Test + public void testListTopics() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + List topicList = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listTopics(); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(topicList.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testListTopicsNextPage() { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(2); + ListTopicsRequest request1 = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + ListTopicsRequest request2 = ListTopicsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageToken(cursor1) + .build(); + List topicList1 = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + List topicList2 = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response1 = ListTopicsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllTopics(Lists.transform(topicList1, TOPIC_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListTopicsResponse response2 = ListTopicsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllTopics(Lists.transform(topicList2, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listTopics(); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(topicList1.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + page = page.getNextPage(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(topicList2.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testListTopicsEmpty() { + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + List topicList = ImmutableList.of(); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listTopics(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(topicList.toArray(), Iterators.toArray(page.iterateAll(), Topic.class)); + } + + @Test + public void testListTopicsWithOptions() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List topicList = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listTopics(ListOption.pageSize(42), ListOption.pageToken(cursor)); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(topicList.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testListTopicsAsync() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + List topicList = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listTopicsAsync().get(); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(topicList.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testListTopicsAsyncNextPage() throws ExecutionException, InterruptedException { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(2); + ListTopicsRequest request1 = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + ListTopicsRequest request2 = ListTopicsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageToken(cursor1) + .build(); + List topicList1 = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + List topicList2 = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response1 = ListTopicsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllTopics(Lists.transform(topicList1, TOPIC_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListTopicsResponse response2 = ListTopicsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllTopics(Lists.transform(topicList2, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listTopicsAsync().get(); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(topicList1.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + page = page.getNextPageAsync().get(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(topicList2.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testListTopicsAsyncEmpty() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder().setProject(PROJECT_PB).build(); + List topicList = ImmutableList.of(); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listTopicsAsync().get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPageAsync().get()); + assertNull(page.getNextPage()); + assertArrayEquals(topicList.toArray(), Iterators.toArray(page.iterateAll(), Topic.class)); + } + + @Test + public void testListTopicsAsyncWithOptions() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListTopicsRequest request = ListTopicsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List topicList = ImmutableList.of( + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO)), + new Topic(pubsub, new TopicInfo.BuilderImpl(TOPIC_INFO))); + ListTopicsResponse response = ListTopicsResponse.newBuilder() + .setNextPageToken("") + .addAllTopics(Lists.transform(topicList, TOPIC_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = + pubsub.listTopicsAsync(ListOption.pageSize(42), ListOption.pageToken(cursor)).get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPageAsync().get()); + assertArrayEquals(topicList.toArray(), Iterables.toArray(page.getValues(), Topic.class)); + } + + @Test + public void testPublishOneMessage() { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addAllMessages(ImmutableList.of(MESSAGE.toPb())) + .build(); + String messageId = "messageId"; + PublishResponse response = PublishResponse.newBuilder().addMessageIds(messageId).build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageId, pubsub.publish(TOPIC, MESSAGE)); + } + + @Test + public void testPublishOneMessageAsync() throws ExecutionException, InterruptedException { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addMessages(MESSAGE.toPb()) + .build(); + String messageId = "messageId"; + PublishResponse response = PublishResponse.newBuilder().addMessageIds(messageId).build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageId, pubsub.publishAsync(TOPIC, MESSAGE).get()); + } + + @Test + public void testPublishMoreMessages() { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addAllMessages(ImmutableList.of(MESSAGE.toPb(), MESSAGE.toPb())) + .build(); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + PublishResponse response = PublishResponse.newBuilder() + .addAllMessageIds(messageIds) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageIds, pubsub.publish(TOPIC, MESSAGE, MESSAGE)); + } + + @Test + public void testPublishMoreMessagesAsync() throws ExecutionException, InterruptedException { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addAllMessages(ImmutableList.of(MESSAGE.toPb(), MESSAGE.toPb())) + .build(); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + PublishResponse response = PublishResponse.newBuilder() + .addAllMessageIds(messageIds) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageIds, pubsub.publishAsync(TOPIC, MESSAGE, MESSAGE).get()); + } + + @Test + public void testPublishMessageList() { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addAllMessages(ImmutableList.of(MESSAGE.toPb(), MESSAGE.toPb())) + .build(); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + PublishResponse response = PublishResponse.newBuilder() + .addAllMessageIds(messageIds) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageIds, pubsub.publish(TOPIC, ImmutableList.of(MESSAGE, MESSAGE))); + } + + @Test + public void testPublishMessageListAsync() throws ExecutionException, InterruptedException { + PublishRequest request = PublishRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .addAllMessages(ImmutableList.of(MESSAGE.toPb(), MESSAGE.toPb())) + .build(); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + PublishResponse response = PublishResponse.newBuilder() + .addAllMessageIds(messageIds) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.publish(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertEquals(messageIds, pubsub.publishAsync(TOPIC, ImmutableList.of(MESSAGE, MESSAGE)).get()); + } + + @Test + public void testCreateSubscription() { + com.google.pubsub.v1.Subscription subscriptionPb = SUBSCRIPTION_INFO.toPb(PROJECT); + Future response = + Futures.immediateFuture(subscriptionPb); + EasyMock.expect(pubsubRpcMock.create(subscriptionPb)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Subscription subscription = pubsub.create(SUBSCRIPTION_INFO); + assertEquals( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + subscription); + } + + @Test + public void testCreateSubscriptionAsync() throws ExecutionException, InterruptedException { + com.google.pubsub.v1.Subscription subscriptionPb = SUBSCRIPTION_INFO.toPb(PROJECT); + Future response = + Futures.immediateFuture(subscriptionPb); + EasyMock.expect(pubsubRpcMock.create(subscriptionPb)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Subscription subscription = pubsub.createAsync(SUBSCRIPTION_INFO).get(); + assertEquals( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + subscription); + } + + @Test + public void testGetSubscription() { + GetSubscriptionRequest request = + GetSubscriptionRequest.newBuilder().setSubscription(SUBSCRIPTION_NAME_PB).build(); + Future response = + Futures.immediateFuture(SUBSCRIPTION_INFO.toPb(PROJECT)); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Subscription subscription = pubsub.getSubscription(SUBSCRIPTION); + assertEquals( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + subscription); + } + + @Test + public void testGetSubscription_Null() { + GetSubscriptionRequest request = + GetSubscriptionRequest.newBuilder().setSubscription(SUBSCRIPTION_NAME_PB).build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getSubscription(SUBSCRIPTION)); + } + + @Test + public void testGetSubscriptionAsync() throws ExecutionException, InterruptedException { + GetSubscriptionRequest request = + GetSubscriptionRequest.newBuilder().setSubscription(SUBSCRIPTION_NAME_PB).build(); + Future response = + Futures.immediateFuture(SUBSCRIPTION_INFO.toPb(PROJECT)); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Subscription subscription = pubsub.getSubscriptionAsync(SUBSCRIPTION).get(); + assertEquals( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + subscription); + } + + @Test + public void testGetSubscriptionAsync_Null() throws ExecutionException, InterruptedException { + GetSubscriptionRequest request = + GetSubscriptionRequest.newBuilder().setSubscription(SUBSCRIPTION_NAME_PB).build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.get(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getSubscriptionAsync(SUBSCRIPTION).get()); + } + + @Test + public void testDeleteSubscription() { + DeleteSubscriptionRequest request = DeleteSubscriptionRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertTrue(pubsub.deleteSubscription(SUBSCRIPTION)); + } + + @Test + public void testDeleteSubscription_Null() { + DeleteSubscriptionRequest request = DeleteSubscriptionRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertFalse(pubsub.deleteSubscription(SUBSCRIPTION)); + } + + @Test + public void testDeleteSubscriptionAsync() throws ExecutionException, InterruptedException { + DeleteSubscriptionRequest request = DeleteSubscriptionRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertTrue(pubsub.deleteSubscriptionAsync(SUBSCRIPTION).get()); + } + + @Test + public void testDeleteSubscriptionAsync_Null() throws ExecutionException, InterruptedException { + DeleteSubscriptionRequest request = DeleteSubscriptionRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .build(); + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.delete(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertFalse(pubsub.deleteSubscriptionAsync(SUBSCRIPTION).get()); + } + + @Test + public void testReplacePushConfig() { + ModifyPushConfigRequest request = ModifyPushConfigRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setPushConfig(PUSH_CONFIG.toPb()) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + pubsub.replacePushConfig(SUBSCRIPTION, PUSH_CONFIG); + } + + @Test + public void testReplacePushConfig_Null() { + ModifyPushConfigRequest request = ModifyPushConfigRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setPushConfig(com.google.pubsub.v1.PushConfig.getDefaultInstance()) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + pubsub.replacePushConfig(SUBSCRIPTION, null); + } + + @Test + public void testReplacePushConfigAsync() throws ExecutionException, InterruptedException { + ModifyPushConfigRequest request = ModifyPushConfigRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setPushConfig(PUSH_CONFIG.toPb()) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + pubsub.replacePushConfigAsync(SUBSCRIPTION, PUSH_CONFIG).get(); + } + + @Test + public void testReplacePushConfigAsync_Null() throws ExecutionException, InterruptedException { + ModifyPushConfigRequest request = ModifyPushConfigRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setPushConfig(com.google.pubsub.v1.PushConfig.getDefaultInstance()) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + pubsub.replacePushConfigAsync(SUBSCRIPTION, null).get(); + } + + @Test + public void testListSubscriptions() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + List subscriptionList = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsNextPage() { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(2); + ListSubscriptionsRequest request1 = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + ListSubscriptionsRequest request2 = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageToken(cursor1) + .build(); + List subscriptionList1 = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + List subscriptionList2 = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response1 = ListSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllSubscriptions(Lists.transform(subscriptionList1, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListSubscriptionsResponse response2 = ListSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllSubscriptions(Lists.transform(subscriptionList2, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(subscriptionList1.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + page = page.getNextPage(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(subscriptionList2.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsEmpty() { + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + List subscriptionList = ImmutableList.of(); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsWithOptions() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List subscriptionList = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = + pubsub.listSubscriptions(ListOption.pageSize(42), ListOption.pageToken(cursor)); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsAsync() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + List subscriptionList = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync().get(); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsAsyncNextPage() throws ExecutionException, InterruptedException { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(2); + ListSubscriptionsRequest request1 = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + ListSubscriptionsRequest request2 = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageToken(cursor1) + .build(); + List subscriptionList1 = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + List subscriptionList2 = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response1 = ListSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllSubscriptions(Lists.transform(subscriptionList1, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListSubscriptionsResponse response2 = ListSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllSubscriptions(Lists.transform(subscriptionList2, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync().get(); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(subscriptionList1.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + page = page.getNextPageAsync().get(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(subscriptionList2.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsAsyncEmpty() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .build(); + List subscriptionList = ImmutableList.of(); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync().get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPageAsync().get()); + assertNull(page.getNextPage()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListSubscriptionsAsyncWithOptions() + throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + resetOptionsForList(1); + ListSubscriptionsRequest request = ListSubscriptionsRequest.newBuilder() + .setProject(PROJECT_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List subscriptionList = ImmutableList.of( + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO)), + new Subscription(pubsub, new SubscriptionInfo.BuilderImpl(COMPLETE_SUBSCRIPTION_INFO))); + ListSubscriptionsResponse response = ListSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_TO_PB_FUNCTION)) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = + pubsub.listSubscriptionsAsync(ListOption.pageSize(42), ListOption.pageToken(cursor)).get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertNull(page.getNextPageAsync().get()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), Subscription.class)); + } + + @Test + public void testListTopicSubscriptions() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + List subscriptionList = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(TOPIC); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsNextPage() { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request1 = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + ListTopicSubscriptionsRequest request2 = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .setPageToken(cursor1) + .build(); + List subscriptionList1 = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + List subscriptionList2 = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription3")); + ListTopicSubscriptionsResponse response1 = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllSubscriptions(Lists.transform(subscriptionList1, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListTopicSubscriptionsResponse response2 = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllSubscriptions(Lists.transform(subscriptionList2, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = + Futures.immediateFuture(response1); + Future futureResponse2 = + Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(TOPIC); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(subscriptionList1.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + page = page.getNextPage(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(subscriptionList2.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsEmpty() { + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + List subscriptionList = ImmutableList.of(); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = pubsub.listSubscriptions(TOPIC); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsWithOptions() { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List subscriptionList = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + Page page = + pubsub.listSubscriptions(TOPIC, ListOption.pageSize(42), ListOption.pageToken(cursor)); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsAsync() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + List subscriptionList = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("cursor") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync(TOPIC).get(); + assertEquals(cursor, page.getNextPageCursor()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsAsyncNextPage() + throws ExecutionException, InterruptedException { + String cursor1 = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request1 = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + ListTopicSubscriptionsRequest request2 = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .setPageToken(cursor1) + .build(); + List subscriptionList1 = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + List subscriptionList2 = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription3")); + ListTopicSubscriptionsResponse response1 = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllSubscriptions(Lists.transform(subscriptionList1, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + String cursor2 = "nextCursor"; + ListTopicSubscriptionsResponse response2 = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllSubscriptions(Lists.transform(subscriptionList2, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse1 = + Futures.immediateFuture(response1); + Future futureResponse2 = + Futures.immediateFuture(response2); + EasyMock.expect(pubsubRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(pubsubRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync(TOPIC).get(); + assertEquals(cursor1, page.getNextPageCursor()); + assertArrayEquals(subscriptionList1.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + page = page.getNextPageAsync().get(); + assertEquals(cursor2, page.getNextPageCursor()); + assertArrayEquals(subscriptionList2.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsAsyncEmpty() + throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .build(); + List subscriptionList = ImmutableList.of(); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync(TOPIC).get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertNull(page.getNextPageAsync().get()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testListTopicSubscriptionsAsyncWithOptions() + throws ExecutionException, InterruptedException { + String cursor = "cursor"; + pubsub = new PubSubImpl(options, renewerMock); + ListTopicSubscriptionsRequest request = ListTopicSubscriptionsRequest.newBuilder() + .setTopic(TOPIC_NAME_PB) + .setPageSize(42) + .setPageToken(cursor) + .build(); + List subscriptionList = ImmutableList.of( + new SubscriptionId(PROJECT, "subscription1"), + new SubscriptionId(PROJECT, "subscription2")); + ListTopicSubscriptionsResponse response = ListTopicSubscriptionsResponse.newBuilder() + .setNextPageToken("") + .addAllSubscriptions(Lists.transform(subscriptionList, SUBSCRIPTION_ID_TO_PB_FUNCTION)) + .build(); + Future futureResponse = + Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(pubsubRpcMock, renewerMock); + AsyncPage page = pubsub.listSubscriptionsAsync( + TOPIC, ListOption.pageSize(42), ListOption.pageToken(cursor)).get(); + assertNull(page.getNextPageCursor()); + assertNull(page.getNextPage()); + assertNull(page.getNextPageAsync().get()); + assertArrayEquals(subscriptionList.toArray(), + Iterables.toArray(page.getValues(), SubscriptionId.class)); + } + + @Test + public void testPullMessages() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(42) + .setReturnImmediately(true) + .build(); + List messageList = ImmutableList.of( + ReceivedMessage.fromPb(pubsub, SUBSCRIPTION, MESSAGE_PB1), + ReceivedMessage.fromPb(pubsub, SUBSCRIPTION, MESSAGE_PB2)); + PullResponse response = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE_PB1) + .addReceivedMessages(MESSAGE_PB2) + .build(); + Capture callback = Capture.newInstance(); + PullFuture futureMock = EasyMock.createStrictMock(PullFuture.class); + futureMock.addCallback(EasyMock.capture(callback)); + EasyMock.expectLastCall(); + EasyMock.expect(futureMock.get()).andReturn(response); + EasyMock.expect(pubsubRpcMock.pull(request)).andReturn(futureMock); + renewerMock.add(SUBSCRIPTION, ImmutableList.of("ackId1", "ackId2")); + EasyMock.replay(pubsubRpcMock, renewerMock, futureMock); + Iterator messageIterator = pubsub.pull(SUBSCRIPTION, 42); + callback.getValue().success(response); + EasyMock.reset(renewerMock); + for (ReceivedMessage message : messageList) { + renewerMock.remove(SUBSCRIPTION, message.getAckId()); + EasyMock.expectLastCall(); + } + EasyMock.replay(renewerMock); + while (messageIterator.hasNext()) { + messageIterator.next(); + } + EasyMock.verify(futureMock); + } + + @Test + public void testPullMessagesAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(42) + .setReturnImmediately(false) + .build(); + List messageList = ImmutableList.of( + ReceivedMessage.fromPb(pubsub, SUBSCRIPTION, MESSAGE_PB1), + ReceivedMessage.fromPb(pubsub, SUBSCRIPTION, MESSAGE_PB2)); + PullResponse response = PullResponse.newBuilder() + .addReceivedMessages(MESSAGE_PB1) + .addReceivedMessages(MESSAGE_PB2) + .build(); + Capture callback = Capture.newInstance(); + PullFuture futureMock = EasyMock.createStrictMock(PullFuture.class); + futureMock.addCallback(EasyMock.capture(callback)); + EasyMock.expectLastCall(); + EasyMock.expect(futureMock.get()).andReturn(response); + EasyMock.expect(pubsubRpcMock.pull(request)).andReturn(futureMock); + renewerMock.add(SUBSCRIPTION, ImmutableList.of("ackId1", "ackId2")); + EasyMock.replay(pubsubRpcMock, renewerMock, futureMock); + Iterator messageIterator = pubsub.pullAsync(SUBSCRIPTION, 42).get(); + callback.getValue().success(response); + EasyMock.reset(renewerMock); + for (ReceivedMessage message : messageList) { + renewerMock.remove(SUBSCRIPTION, message.getAckId()); + EasyMock.expectLastCall(); + } + EasyMock.replay(renewerMock); + while (messageIterator.hasNext()) { + messageIterator.next(); + } + EasyMock.verify(futureMock); + } + + @Test + public void testPullMessagesError() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(42) + .setReturnImmediately(true) + .build(); + PubSubException exception = new PubSubException(new IOException(), false); + PullFuture futureMock = EasyMock.createStrictMock(PullFuture.class); + futureMock.addCallback(EasyMock.anyObject(PullCallback.class)); + EasyMock.expectLastCall(); + EasyMock.expect(futureMock.get()).andThrow(new ExecutionException(exception)); + EasyMock.expect(pubsubRpcMock.pull(request)).andReturn(futureMock); + EasyMock.replay(pubsubRpcMock, renewerMock, futureMock); + try { + pubsub.pull(SUBSCRIPTION, 42); + fail("Expected PubSubException"); + } catch (PubSubException ex) { + assertSame(exception, ex); + } + EasyMock.verify(futureMock); + } + + @Test + public void testPullMessagesAsyncError() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(42) + .setReturnImmediately(false) + .build(); + PubSubException exception = new PubSubException(new IOException(), false); + PullFuture futureMock = EasyMock.createStrictMock(PullFuture.class); + futureMock.addCallback(EasyMock.anyObject(PullCallback.class)); + EasyMock.expectLastCall(); + EasyMock.expect(futureMock.get()).andThrow(new ExecutionException(exception)); + EasyMock.expect(pubsubRpcMock.pull(request)).andReturn(futureMock); + EasyMock.replay(pubsubRpcMock, renewerMock, futureMock); + try { + pubsub.pullAsync(SUBSCRIPTION, 42).get(); + fail("Expected ExecutionException"); + } catch (ExecutionException ex) { + assertSame(exception, ex.getCause()); + } + EasyMock.verify(futureMock); + } + + @Test + public void testMessageConsumer() throws Exception { + pubsub = new PubSubImpl(options, renewerMock); + EasyMock.reset(options); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpcMock); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT); + EasyMock.replay(options); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(100) + .setReturnImmediately(false) + .build(); + final PullResponse response = PullResponse.getDefaultInstance(); + final CountDownLatch latch = new CountDownLatch(1); + EasyMock.expect(pubsubRpcMock.pull(request)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + latch.countDown(); + return new TestPullFuture(response); + } + }); + EasyMock.replay(pubsubRpcMock, renewerMock); + try (MessageConsumer consumer = pubsub.pullAsync(SUBSCRIPTION, DO_NOTHING)) { + latch.await(); + } + } + + @Test + public void testMessageConsumerWithOptions() throws Exception { + pubsub = new PubSubImpl(options, renewerMock); + EasyMock.reset(options); + EasyMock.expect(options.getService()).andReturn(pubsub); + EasyMock.expect(options.getRpc()).andReturn(pubsubRpcMock); + EasyMock.expect(options.getProjectId()).andReturn(PROJECT); + EasyMock.replay(options); + ExecutorFactory executorFactoryMock = EasyMock.createStrictMock(ExecutorFactory.class); + ExecutorService executorServiceMock = EasyMock.createStrictMock(ExecutorService.class); + EasyMock.expect(executorFactoryMock.get()).andReturn(executorServiceMock); + executorFactoryMock.release(executorServiceMock); + PullRequest request = PullRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .setMaxMessages(42) + .setReturnImmediately(false) + .build(); + final PullResponse response = PullResponse.getDefaultInstance(); + final CountDownLatch latch = new CountDownLatch(1); + EasyMock.expect(pubsubRpcMock.pull(request)).andAnswer(new IAnswer() { + @Override + public PullFuture answer() throws Throwable { + latch.countDown(); + return new TestPullFuture(response); + } + }); + EasyMock.replay(pubsubRpcMock, renewerMock, executorFactoryMock, executorServiceMock); + PullOption[] options = + {PullOption.maxQueuedCallbacks(42), PullOption.executorFactory(executorFactoryMock)}; + try (MessageConsumer consumer = pubsub.pullAsync(SUBSCRIPTION, DO_NOTHING, options)) { + latch.await(); + } + } + + @Test + public void testAckOneMessage() { + pubsub = new PubSubImpl(options, renewerMock); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.ack(SUBSCRIPTION, "ackId"); + } + + @Test + public void testAckOneMessageAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.ackAsync(SUBSCRIPTION, "ackId"); + assertNull(future.get()); + } + + @Test + public void testAckMoreMessages() { + pubsub = new PubSubImpl(options, renewerMock); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.ack(SUBSCRIPTION, "ackId1", "ackId2"); + } + + @Test + public void testAckMoreMessagesAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.ackAsync(SUBSCRIPTION, "ackId1", "ackId2"); + assertNull(future.get()); + } + + @Test + public void testAckMessageList() { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.ack(SUBSCRIPTION, ackIds); + } + + @Test + public void testAckMessageListAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + AcknowledgeRequest request = AcknowledgeRequest.newBuilder() + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.acknowledge(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.ackAsync(SUBSCRIPTION, ackIds); + assertNull(future.get()); + } + + @Test + public void testNackOneMessage() { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.nack(SUBSCRIPTION, "ackId"); + } + + @Test + public void testNackOneMessageAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.nackAsync(SUBSCRIPTION, "ackId"); + assertNull(future.get()); + } + + @Test + public void testNackMoreMessages() { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.nack(SUBSCRIPTION, "ackId1", "ackId2"); + } + + @Test + public void testNackMoreMessagesAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.nackAsync(SUBSCRIPTION, "ackId1", "ackId2"); + assertNull(future.get()); + } + + @Test + public void testNackMessageList() { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.nack(SUBSCRIPTION, ackIds); + } + + @Test + public void testNackMessageListAsync() throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(0) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.nackAsync(SUBSCRIPTION, ackIds); + assertNull(future.get()); + } + + @Test + public void testModifyAckDeadlineOneMessage() { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.modifyAckDeadline(SUBSCRIPTION, 10, TimeUnit.SECONDS, "ackId"); + } + + @Test + public void testModifyAckDeadlineOneMessageAsync() + throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAckIds("ackId") + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = + pubsub.modifyAckDeadlineAsync(SUBSCRIPTION, 10, TimeUnit.SECONDS, "ackId"); + assertNull(future.get()); + } + + @Test + public void testModifyAckDeadlineMoreMessages() { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.modifyAckDeadline(SUBSCRIPTION, 10, TimeUnit.SECONDS, "ackId1", "ackId2"); + } + + @Test + public void testModifyAckDeadlineMoreMessagesAsync() + throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ImmutableList.of("ackId1", "ackId2")) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = + pubsub.modifyAckDeadlineAsync(SUBSCRIPTION, 10, TimeUnit.SECONDS, "ackId1", "ackId2"); + assertNull(future.get()); + } + + @Test + public void testModifyAckDeadlineMessageList() { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.modifyAckDeadline(SUBSCRIPTION, 10, TimeUnit.SECONDS, ackIds); + } + + @Test + public void testModifyAckDeadlineMessageListAsync() + throws ExecutionException, InterruptedException { + pubsub = new PubSubImpl(options, renewerMock); + List ackIds = ImmutableList.of("ackId1", "ackId2"); + ModifyAckDeadlineRequest request = ModifyAckDeadlineRequest.newBuilder() + .setAckDeadlineSeconds(10) + .setSubscription(SUBSCRIPTION_NAME_PB) + .addAllAckIds(ackIds) + .build(); + Future response = Futures.immediateFuture(Empty.getDefaultInstance()); + EasyMock.expect(pubsubRpcMock.modify(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + Future future = pubsub.modifyAckDeadlineAsync(SUBSCRIPTION, 10, TimeUnit.SECONDS, ackIds); + assertNull(future.get()); + } + + @Test + public void testGetTopicPolicy() { + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.getIamPolicy(TOPIC_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Policy policy = pubsub.getTopicPolicy(TOPIC); + assertEquals(POLICY, policy); + } + + @Test + public void testGetTopicPolicy_Null() { + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.getIamPolicy(TOPIC_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getTopicPolicy(TOPIC)); + } + + @Test + public void testGetTopicPolicyAsync() throws ExecutionException, InterruptedException { + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.getIamPolicy(TOPIC_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future future = pubsub.getTopicPolicyAsync(TOPIC); + assertEquals(POLICY, future.get()); + } + + @Test + public void testGetTopicPolicyAsync_Null() throws ExecutionException, InterruptedException { + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.getIamPolicy(TOPIC_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getTopicPolicyAsync(TOPIC).get()); + } + + @Test + public void testReplaceTopicPolicy() { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .setPolicy(PolicyMarshaller.INSTANCE.toPb(POLICY)) + .build(); + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.setIamPolicy(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Policy policy = pubsub.replaceTopicPolicy(TOPIC, POLICY); + assertEquals(POLICY, policy); + } + + @Test + public void testReplaceTopicPolicyAsync() throws ExecutionException, InterruptedException { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .setPolicy(PolicyMarshaller.INSTANCE.toPb(POLICY)) + .build(); + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.setIamPolicy(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future future = pubsub.replaceTopicPolicyAsync(TOPIC, POLICY); + assertEquals(POLICY, future.get()); + } + + @Test + public void testTestTopicPermissions() { + List permissions = ImmutableList.of("pubsub.topics.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(permissions) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + List permissionBooleans = pubsub.testTopicPermissions(TOPIC, permissions); + assertEquals(ImmutableList.of(true), permissionBooleans); + } + + @Test + public void testTestTopicNoPermissions() { + List permissions = ImmutableList.of("pubsub.topics.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(ImmutableList.of()) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + List permissionBooleans = pubsub.testTopicPermissions(TOPIC, permissions); + assertEquals(ImmutableList.of(false), permissionBooleans); + } + + @Test + public void testTestTopicPermissionsAsync() throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.topics.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(permissions) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future> future = pubsub.testTopicPermissionsAsync(TOPIC, permissions); + assertEquals(ImmutableList.of(true), future.get()); + } + + @Test + public void testTestTopicNoPermissionsAsync() throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.topics.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(TOPIC_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(ImmutableList.of()) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future> future = pubsub.testTopicPermissionsAsync(TOPIC, permissions); + assertEquals(ImmutableList.of(false), future.get()); + } + + @Test + public void testGetSubscriptionPolicy() { + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.getIamPolicy(SUBSCRIPTION_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Policy policy = pubsub.getSubscriptionPolicy(SUBSCRIPTION); + assertEquals(POLICY, policy); + } + + @Test + public void testGetSubscriptionPolicy_Null() { + Future response = Futures.immediateFuture(null); + EasyMock.expect(pubsubRpcMock.getIamPolicy(SUBSCRIPTION_NAME_PB)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + assertNull(pubsub.getSubscriptionPolicy(SUBSCRIPTION)); + } + + @Test + public void testReplaceSubscriptionPolicy() { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .setPolicy(PolicyMarshaller.INSTANCE.toPb(POLICY)) + .build(); + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.setIamPolicy(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Policy policy = pubsub.replaceSubscriptionPolicy(SUBSCRIPTION, POLICY); + assertEquals(POLICY, policy); + } + + @Test + public void testReplaceSubscriptionPolicyAsync() throws ExecutionException, InterruptedException { + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .setPolicy(PolicyMarshaller.INSTANCE.toPb(POLICY)) + .build(); + Future response = Futures.immediateFuture(POLICY_PB); + EasyMock.expect(pubsubRpcMock.setIamPolicy(request)).andReturn(response); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future future = pubsub.replaceSubscriptionPolicyAsync(SUBSCRIPTION, POLICY); + assertEquals(POLICY, future.get()); + } + + @Test + public void testTestSubscriptionPermissions() { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(permissions) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + List permissionBooleans = + pubsub.testSubscriptionPermissions(SUBSCRIPTION, permissions); + assertEquals(ImmutableList.of(true), permissionBooleans); + } + + @Test + public void testTestSubscriptionNoPermissions() { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(ImmutableList.of()) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + List permissionBooleans = + pubsub.testSubscriptionPermissions(SUBSCRIPTION, permissions); + assertEquals(ImmutableList.of(false), permissionBooleans); + } + + @Test + public void testTestSubscriptionPermissionsAsync() + throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(permissions) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future> future = + pubsub.testSubscriptionPermissionsAsync(SUBSCRIPTION, permissions); + assertEquals(ImmutableList.of(true), future.get()); + } + + @Test + public void testTestSubscriptionNoPermissionsAsync() + throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(SUBSCRIPTION_NAME_PB) + .addAllPermissions(permissions) + .build(); + TestIamPermissionsResponse response = TestIamPermissionsResponse.newBuilder() + .addAllPermissions(ImmutableList.of()) + .build(); + Future responseFuture = Futures.immediateFuture(response); + EasyMock.expect(pubsubRpcMock.testIamPermissions(request)).andReturn(responseFuture); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub = new PubSubImpl(options, renewerMock); + Future> future = + pubsub.testSubscriptionPermissionsAsync(SUBSCRIPTION, permissions); + assertEquals(ImmutableList.of(false), future.get()); + } + + @Test + public void testClose() throws Exception { + pubsub = new PubSubImpl(options, renewerMock); + pubsubRpcMock.close(); + EasyMock.expectLastCall(); + EasyMock.expectLastCall(); + renewerMock.close(); + EasyMock.expectLastCall(); + EasyMock.replay(pubsubRpcMock, renewerMock); + pubsub.close(); + // closing again should do nothing + pubsub.close(); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubTest.java new file mode 100644 index 000000000000..78322f4eed95 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PubSubTest.java @@ -0,0 +1,59 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.cloud.pubsub.PubSub.PullOption; + +import org.easymock.EasyMock; +import org.junit.Test; + +public class PubSubTest { + + private static final int PAGE_SIZE = 42; + private static final String PAGE_TOKEN = "page token"; + private static final int MAX_QUEUED_CALLBACKS = 42; + + @Test + public void testListOption() { + // page token + ListOption listOption = ListOption.pageToken(PAGE_TOKEN); + assertEquals(PAGE_TOKEN, listOption.getValue()); + assertEquals(ListOption.OptionType.PAGE_TOKEN, listOption.getOptionType()); + // page size + listOption = ListOption.pageSize(PAGE_SIZE); + assertEquals(PAGE_SIZE, listOption.getValue()); + assertEquals(ListOption.OptionType.PAGE_SIZE, listOption.getOptionType()); + } + + @Test + @SuppressWarnings("unchecked") + public void testPullOptions() { + // max queued callbacks + PullOption pullOption = PullOption.maxQueuedCallbacks(MAX_QUEUED_CALLBACKS); + assertEquals(MAX_QUEUED_CALLBACKS, pullOption.getValue()); + assertEquals(PullOption.OptionType.MAX_QUEUED_CALLBACKS, pullOption.getOptionType()); + ExecutorFactory executorFactory = EasyMock.createStrictMock(ExecutorFactory.class); + pullOption = PullOption.executorFactory(executorFactory); + assertSame(executorFactory, pullOption.getValue()); + assertEquals(PullOption.OptionType.EXECUTOR_FACTORY, pullOption.getOptionType()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PushConfigTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PushConfigTest.java new file mode 100644 index 000000000000..baf8ea4d3535 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/PushConfigTest.java @@ -0,0 +1,114 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; + +import com.google.common.collect.ImmutableMap; + +import org.junit.Test; + +import java.util.Map; + +public class PushConfigTest { + + private static final String ENDPOINT = "https://example.com/push"; + private static final Map ATTRIBUTES = + ImmutableMap.of("key1", "value1", "key2", "value2"); + private static final PushConfig PUSH_CONFIG = PushConfig.newBuilder(ENDPOINT, ATTRIBUTES).build(); + private static final PushConfig DEPRECATED_PUSH_CONFIG = + PushConfig.builder(ENDPOINT, ATTRIBUTES).build(); + + @Test + public void testToBuilder() { + comparePushConfig(PUSH_CONFIG, PUSH_CONFIG.toBuilder().build()); + PushConfig pushConfig = PUSH_CONFIG.toBuilder() + .setEndpoint("https://example2.com/push") + .clearAttributes() + .addAttribute("key1", "value1") + .build(); + assertEquals("https://example2.com/push", pushConfig.getEndpoint()); + assertEquals(ImmutableMap.of("key1", "value1"), pushConfig.getAttributes()); + pushConfig = pushConfig.toBuilder() + .setEndpoint(ENDPOINT) + .removeAttribute("key1") + .setAttributes(ATTRIBUTES) + .build(); + comparePushConfig(PUSH_CONFIG, pushConfig); + } + + @Test + public void testBuilder() { + assertEquals(ENDPOINT, DEPRECATED_PUSH_CONFIG.endpoint()); + assertEquals(ATTRIBUTES, DEPRECATED_PUSH_CONFIG.attributes()); + PushConfig pushConfig = PushConfig.builder("https://example2.com/push") + .endpoint(ENDPOINT) + .attributes(ATTRIBUTES) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .build(); + assertEquals(ENDPOINT, pushConfig.endpoint()); + assertEquals(ATTRIBUTES, pushConfig.attributes()); + comparePushConfig(PUSH_CONFIG, pushConfig); + } + + @Test + public void testBuilderDeprecated() { + assertEquals(ENDPOINT, PUSH_CONFIG.getEndpoint()); + assertEquals(ATTRIBUTES, PUSH_CONFIG.getAttributes()); + PushConfig pushConfig = PushConfig.newBuilder("https://example2.com/push") + .setEndpoint(ENDPOINT) + .setAttributes(ATTRIBUTES) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .build(); + assertEquals(ENDPOINT, pushConfig.getEndpoint()); + assertEquals(ATTRIBUTES, pushConfig.getAttributes()); + comparePushConfig(PUSH_CONFIG, pushConfig); + } + + @Test + public void testOf() { + PushConfig pushConfig = PushConfig.of(ENDPOINT); + assertEquals(ENDPOINT, pushConfig.getEndpoint()); + assertEquals(ImmutableMap.of(), pushConfig.getAttributes()); + pushConfig = PushConfig.of(ENDPOINT, ATTRIBUTES); + assertEquals(ENDPOINT, pushConfig.getEndpoint()); + assertEquals(ATTRIBUTES, pushConfig.getAttributes()); + comparePushConfig(PUSH_CONFIG, pushConfig); + } + + @Test + public void testToAndFromPb() { + comparePushConfig(PUSH_CONFIG, PushConfig.fromPb(PUSH_CONFIG.toPb())); + } + + @Test + public void testToAndFromPbIncomplete() { + PushConfig pushConfig = PushConfig.of(ENDPOINT); + comparePushConfig(pushConfig, PushConfig.fromPb(pushConfig.toPb())); + } + + private void comparePushConfig(PushConfig expected, PushConfig value) { + assertEquals(expected, value); + assertEquals(expected.getEndpoint(), value.getEndpoint()); + assertEquals(expected.getAttributes(), value.getAttributes()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/ReceivedMessageTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/ReceivedMessageTest.java new file mode 100644 index 000000000000..c740063c533b --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/ReceivedMessageTest.java @@ -0,0 +1,223 @@ +/* + * 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.pubsub; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import com.google.api.client.util.Charsets; +import com.google.cloud.ByteArray; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; + +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class ReceivedMessageTest { + + private static final String SUBSCRIPTION = "subscription"; + private static final String ACK_ID = "ackId"; + private static final String MESSAGE_ID = "messageId"; + private static final String PAYLOAD_STRING = "payload"; + private static final ByteArray PAYLOAD = + ByteArray.copyFrom("payload".getBytes(StandardCharsets.UTF_8)); + private static final Map ATTRIBUTES = + ImmutableMap.of("key1", "value1", "key2", "value2"); + private static final Long PUBLISH_TIME = 42L; + private static final Message MESSAGE = Message.newBuilder(PAYLOAD) + .setId(MESSAGE_ID) + .setAttributes(ATTRIBUTES) + .setPublishTime(PUBLISH_TIME) + .build(); + private static final com.google.pubsub.v1.ReceivedMessage RECEIVED_MESSAGE_PB = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE.toPb()) + .setAckId(ACK_ID) + .build(); + + private final PubSub serviceMockReturnsOptions = createStrictMock(PubSub.class); + private final PubSubOptions mockOptions = createMock(PubSubOptions.class); + private PubSub pubsub; + private ReceivedMessage expectedMessage; + private ReceivedMessage message; + + private void initializeExpectedMessage(int optionsCalls) { + expect(serviceMockReturnsOptions.getOptions()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + pubsub = createStrictMock(PubSub.class); + expectedMessage = + ReceivedMessage.fromPb(serviceMockReturnsOptions, SUBSCRIPTION, RECEIVED_MESSAGE_PB); + } + + private void initializeMessage() { + message = ReceivedMessage.fromPb(pubsub, SUBSCRIPTION, RECEIVED_MESSAGE_PB); + } + + @After + public void tearDown() throws Exception { + verify(pubsub, serviceMockReturnsOptions); + } + + @Test + public void testBuilder() { + initializeExpectedMessage(3); + replay(pubsub); + Map attributes = ImmutableMap.of("newKey1", "newVal1"); + ReceivedMessage builtMessage = expectedMessage.toBuilder() + .setPayload("newPayload") + .setId("newMessageId") + .setAttributes(attributes) + .setPublishTime(PUBLISH_TIME + 1) + .build(); + assertSame(serviceMockReturnsOptions, builtMessage.getPubsub()); + assertEquals(SUBSCRIPTION, builtMessage.getSubscription()); + assertEquals(ACK_ID, builtMessage.getAckId()); + assertEquals("newMessageId", builtMessage.getId()); + assertArrayEquals("newPayload".getBytes(Charsets.UTF_8), builtMessage.getPayload().toByteArray()); + assertEquals("newPayload", builtMessage.getPayloadAsString()); + assertEquals(attributes, builtMessage.getAttributes()); + assertEquals(PUBLISH_TIME + 1, (long) builtMessage.getPublishTime()); + builtMessage = builtMessage.toBuilder() + .setPayload(PAYLOAD) + .setId(MESSAGE_ID) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .setPublishTime(PUBLISH_TIME) + .build(); + assertSame(serviceMockReturnsOptions, builtMessage.getPubsub()); + assertEquals(MESSAGE_ID, builtMessage.getId()); + assertEquals(PAYLOAD, builtMessage.getPayload()); + assertEquals(PAYLOAD_STRING, builtMessage.getPayloadAsString()); + assertEquals(ATTRIBUTES, builtMessage.getAttributes()); + assertEquals(PUBLISH_TIME, builtMessage.getPublishTime()); + compareReceivedMessage(expectedMessage, builtMessage); + } + + @Test + public void testBuilderDeprecated() { + initializeExpectedMessage(3); + replay(pubsub); + Map attributes = ImmutableMap.of("newKey1", "newVal1"); + ReceivedMessage builtMessage = expectedMessage.toBuilder() + .payload("newPayload") + .setId("newMessageId") + .attributes(attributes) + .setPublishTime(PUBLISH_TIME + 1) + .build(); + assertSame(serviceMockReturnsOptions, builtMessage.pubsub()); + assertEquals(SUBSCRIPTION, builtMessage.subscription()); + assertEquals(ACK_ID, builtMessage.ackId()); + assertEquals("newMessageId", builtMessage.id()); + assertArrayEquals("newPayload".getBytes(Charsets.UTF_8), builtMessage.payload().toByteArray()); + assertEquals("newPayload", builtMessage.payloadAsString()); + assertEquals(attributes, builtMessage.attributes()); + assertEquals(PUBLISH_TIME + 1, (long) builtMessage.publishTime()); + builtMessage = builtMessage.toBuilder() + .payload(PAYLOAD) + .setId(MESSAGE_ID) + .clearAttributes() + .addAttribute("key1", "value1") + .addAttribute("key2", "value2") + .setPublishTime(PUBLISH_TIME) + .build(); + assertSame(serviceMockReturnsOptions, builtMessage.pubsub()); + assertEquals(MESSAGE_ID, builtMessage.id()); + assertEquals(PAYLOAD, builtMessage.payload()); + assertEquals(PAYLOAD_STRING, builtMessage.payloadAsString()); + assertEquals(ATTRIBUTES, builtMessage.attributes()); + assertEquals(PUBLISH_TIME, builtMessage.publishTime()); + compareReceivedMessage(expectedMessage, builtMessage); + } + + @Test + public void testToBuilder() { + initializeExpectedMessage(2); + replay(pubsub); + compareReceivedMessage(expectedMessage, expectedMessage.toBuilder().build()); + } + + @Test + public void testAck() { + initializeExpectedMessage(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + pubsub.ack(SUBSCRIPTION, ACK_ID); + EasyMock.expectLastCall(); + replay(pubsub); + initializeMessage(); + message.ack(); + } + + @Test + public void testAckAsync() throws ExecutionException, InterruptedException { + initializeExpectedMessage(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.ackAsync(SUBSCRIPTION, ACK_ID)).andReturn(Futures.immediateFuture(null)); + EasyMock.expectLastCall(); + replay(pubsub); + initializeMessage(); + assertNull(message.ackAsync().get()); + } + + @Test + public void testModifyAckDeadline() { + initializeExpectedMessage(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + pubsub.modifyAckDeadline(SUBSCRIPTION, 10, TimeUnit.SECONDS, ACK_ID); + EasyMock.expectLastCall(); + replay(pubsub); + initializeMessage(); + message.modifyAckDeadline(10, TimeUnit.SECONDS); + } + + @Test + public void testModifyAckDeadlineAsync() throws ExecutionException, InterruptedException { + initializeExpectedMessage(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.modifyAckDeadlineAsync(SUBSCRIPTION, 10, TimeUnit.SECONDS, ACK_ID)) + .andReturn(Futures.immediateFuture(null)); + EasyMock.expectLastCall(); + replay(pubsub); + initializeMessage(); + assertNull(message.modifyAckDeadlineAsync(10, TimeUnit.SECONDS).get()); + } + + private void compareReceivedMessage(ReceivedMessage expected, ReceivedMessage value) { + assertEquals(expected, value); + assertEquals(expected.getId(), value.getId()); + assertEquals(expected.getPayload(), value.getPayload()); + assertEquals(expected.getPayloadAsString(), value.getPayloadAsString()); + assertEquals(expected.getAttributes(), value.getAttributes()); + assertEquals(expected.getPublishTime(), value.getPublishTime()); + assertEquals(expected.getAckId(), value.getAckId()); + assertEquals(expected.getSubscription(), value.getSubscription()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SerializationTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SerializationTest.java new file mode 100644 index 000000000000..b200b42989b1 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SerializationTest.java @@ -0,0 +1,102 @@ +/* + * 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.pubsub; + +import com.google.cloud.BaseSerializationTest; +import com.google.cloud.GrpcServiceOptions.ExecutorFactory; +import com.google.cloud.NoCredentials; +import com.google.cloud.Restorable; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.cloud.pubsub.PubSub.PullOption; + +import java.io.Serializable; +import java.util.concurrent.ScheduledExecutorService; + +public class SerializationTest extends BaseSerializationTest { + + private static final PubSub PUB_SUB = PubSubOptions.newBuilder() + .setProjectId("p") + .setCredentials(NoCredentials.getInstance()) + .setHost("localhost") + .build().getService(); + private static final Message MESSAGE = Message.of("payload"); + private static final com.google.pubsub.v1.ReceivedMessage RECEIVED_MESSAGE_PB = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE.toPb()) + .setAckId("ackId") + .build(); + private static final ReceivedMessage RECEIVED_MESSAGE = + ReceivedMessage.fromPb(PUB_SUB, "subscription", RECEIVED_MESSAGE_PB); + private static final SubscriptionInfo SUBSCRIPTION_INFO = SubscriptionInfo.of("topic", "sub"); + private static final Subscription SUBSCRIPTION = + new Subscription(PUB_SUB, new SubscriptionInfo.BuilderImpl(SUBSCRIPTION_INFO)); + private static final SubscriptionId SUBSCRIPTION_ID = new SubscriptionId("project", "sub"); + private static final TopicInfo TOPIC_INFO = TopicInfo.of("topic"); + private static final Topic TOPIC = + new Topic(PUB_SUB, new TopicInfo.BuilderImpl(TOPIC_INFO)); + private static final ListOption PAGE_TOKEN_OPTION = ListOption.pageToken("cursor"); + private static final ListOption PAGE_SIZE_OPTION = ListOption.pageSize(42); + private static final PullOption MAX_QUEUED_CALLBACKS_OPTION = PullOption.maxQueuedCallbacks(42); + private static final PullOption EXECUTOR_FACTORY_OPTION = + PullOption.executorFactory(new TestExecutorFactory()); + + public static class TestExecutorFactory + implements ExecutorFactory, Serializable { + + private static final long serialVersionUID = -2154875338174302704L; + + @Override + public ScheduledExecutorService get() { + return null; + } + + @Override + public void release(ScheduledExecutorService executor) { + // do nothing + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TestExecutorFactory; + } + + @Override + public int hashCode() { + return 1; + } + } + + @Override + protected Serializable[] serializableObjects() { + PubSubOptions options = PubSubOptions.newBuilder() + .setProjectId("p1") + .setInitialTimeout(1234) + .build(); + PubSubOptions otherOptions = options.toBuilder() + .setProjectId("p2") + .setExecutorFactory(new TestExecutorFactory()) + .build(); + return new Serializable[]{options, otherOptions, MESSAGE, RECEIVED_MESSAGE, SUBSCRIPTION_INFO, + SUBSCRIPTION, SUBSCRIPTION_ID, TOPIC_INFO, TOPIC, PAGE_TOKEN_OPTION, PAGE_SIZE_OPTION, + MAX_QUEUED_CALLBACKS_OPTION, EXECUTOR_FACTORY_OPTION}; + } + + @Override + protected Restorable[] restorableObjects() { + return null; + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionIdTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionIdTest.java new file mode 100644 index 000000000000..578794663b74 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionIdTest.java @@ -0,0 +1,56 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SubscriptionIdTest { + + private static final String PROJECT = "project"; + private static final String NAME = "subscription"; + private static final String TOPIC_PB = "projects/project/subscriptions/subscription"; + private static final SubscriptionId SUBSCRIPTION_ID = new SubscriptionId(PROJECT, NAME); + + @Test + public void testConstructor() { + assertEquals(PROJECT, SUBSCRIPTION_ID.getProject()); + assertEquals(NAME, SUBSCRIPTION_ID.getSubscription()); + } + + @Test + public void testConstructorDeprecated() { + assertEquals(PROJECT, SUBSCRIPTION_ID.project()); + assertEquals(NAME, SUBSCRIPTION_ID.subscription()); + } + + @Test + public void testToAndFromPb() { + SubscriptionId subscriptionId = SubscriptionId.fromPb(TOPIC_PB); + compareSubscriptionId(SUBSCRIPTION_ID, subscriptionId); + assertEquals(PROJECT, subscriptionId.getProject()); + assertEquals(NAME, subscriptionId.getSubscription()); + } + + private void compareSubscriptionId(SubscriptionId expected, SubscriptionId value) { + assertEquals(expected, value); + assertEquals(expected.getProject(), value.getProject()); + assertEquals(expected.getSubscription(), value.getSubscription()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionInfoTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionInfoTest.java new file mode 100644 index 000000000000..d17f29e78dbf --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionInfoTest.java @@ -0,0 +1,162 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class SubscriptionInfoTest { + + private static final TopicId TOPIC = TopicId.of("project", "topic"); + private static final String NAME = "subscription"; + private static final String ENDPOINT = "https://example.com/push"; + private static final PushConfig PUSH_CONFIG = PushConfig.of(ENDPOINT); + private static final int ACK_DEADLINE = 42; + private static final SubscriptionInfo SUBSCRIPTION_INFO = SubscriptionInfo.newBuilder(TOPIC, NAME) + .setPushConfig(PUSH_CONFIG) + .setAckDeadLineSeconds(ACK_DEADLINE) + .build(); + private static final SubscriptionInfo DEPRECATED_SUBSCRIPTION_INFO = + SubscriptionInfo.builder(TOPIC, NAME) + .pushConfig(PUSH_CONFIG) + .ackDeadLineSeconds(ACK_DEADLINE) + .build(); + + @Test + public void testToBuilder() { + compareSubscriptionInfo(SUBSCRIPTION_INFO, SUBSCRIPTION_INFO.toBuilder().build()); + SubscriptionInfo subscriptionInfo = SUBSCRIPTION_INFO.toBuilder() + .setTopic("newTopic") + .setName("newSubscription") + .build(); + assertEquals(TopicId.of("newTopic"), subscriptionInfo.getTopic()); + assertEquals("newSubscription", subscriptionInfo.getName()); + subscriptionInfo = subscriptionInfo.toBuilder().setName(NAME).setTopic(TOPIC).build(); + compareSubscriptionInfo(SUBSCRIPTION_INFO, subscriptionInfo); + } + + @Test + public void testBuilder() { + assertEquals(TOPIC, SUBSCRIPTION_INFO.getTopic()); + assertEquals(NAME, SUBSCRIPTION_INFO.getName()); + assertEquals(PUSH_CONFIG, SUBSCRIPTION_INFO.getPushConfig()); + assertEquals(ACK_DEADLINE, SUBSCRIPTION_INFO.getAckDeadlineSeconds()); + SubscriptionInfo subscriptionInfo = + SubscriptionInfo.newBuilder("topic", "subscription").build(); + assertEquals(TopicId.of("topic"), subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertNull(subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.newBuilder("topic", "subscription") + .setTopic("project", "topic").build(); + assertEquals(TOPIC, subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertNull(subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.newBuilder("topic", "subscription") + .setTopic(TOPIC).build(); + assertEquals(TOPIC, subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertNull(subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + } + + @Test + public void testBuilderDeprecated() { + assertEquals(TOPIC, DEPRECATED_SUBSCRIPTION_INFO.topic()); + assertEquals(NAME, DEPRECATED_SUBSCRIPTION_INFO.name()); + assertEquals(PUSH_CONFIG, DEPRECATED_SUBSCRIPTION_INFO.pushConfig()); + assertEquals(ACK_DEADLINE, DEPRECATED_SUBSCRIPTION_INFO.ackDeadlineSeconds()); + SubscriptionInfo subscriptionInfo = SubscriptionInfo.builder("topic", "subscription").build(); + assertEquals(TopicId.of("topic"), subscriptionInfo.topic()); + assertEquals(NAME, subscriptionInfo.name()); + assertNull(subscriptionInfo.pushConfig()); + assertEquals(0, subscriptionInfo.ackDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.builder("topic", "subscription") + .topic("project", "topic").build(); + assertEquals(TOPIC, subscriptionInfo.topic()); + assertEquals(NAME, subscriptionInfo.name()); + assertNull(subscriptionInfo.pushConfig()); + assertEquals(0, subscriptionInfo.ackDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.builder("topic", "subscription") + .topic(TOPIC).build(); + assertEquals(TOPIC, subscriptionInfo.topic()); + assertEquals(NAME, subscriptionInfo.name()); + assertNull(subscriptionInfo.pushConfig()); + assertEquals(0, subscriptionInfo.ackDeadlineSeconds()); + } + + @Test + public void testOf() { + SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(TOPIC, NAME); + assertEquals(TOPIC, subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertNull(subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.of("topic", NAME); + assertEquals(TopicId.of("topic"), subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertNull(subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.of(TOPIC, NAME, ENDPOINT); + assertEquals(TOPIC, subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertEquals(PushConfig.of(ENDPOINT), subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + subscriptionInfo = SubscriptionInfo.of("topic", NAME, ENDPOINT); + assertEquals(TopicId.of("topic"), subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertEquals(PushConfig.of(ENDPOINT), subscriptionInfo.getPushConfig()); + assertEquals(0, subscriptionInfo.getAckDeadlineSeconds()); + } + + @Test + public void testToAndFromPb() { + compareSubscriptionInfo(SUBSCRIPTION_INFO, + SubscriptionInfo.fromPb(SUBSCRIPTION_INFO.toPb("project"))); + SubscriptionInfo subscriptionInfo = SubscriptionInfo.of(TOPIC, NAME); + compareSubscriptionInfo(subscriptionInfo, + SubscriptionInfo.fromPb(subscriptionInfo.toPb("project"))); + subscriptionInfo = SubscriptionInfo.of("topic", NAME); + compareSubscriptionInfo(SubscriptionInfo.of(TOPIC, NAME), + SubscriptionInfo.fromPb(subscriptionInfo.toPb("project"))); + subscriptionInfo = SubscriptionInfo.of(TOPIC, NAME, ENDPOINT); + compareSubscriptionInfo(subscriptionInfo, + SubscriptionInfo.fromPb(subscriptionInfo.toPb("project"))); + subscriptionInfo = SubscriptionInfo.of("topic", NAME, ENDPOINT); + compareSubscriptionInfo(SubscriptionInfo.of(TOPIC, NAME, ENDPOINT), + SubscriptionInfo.fromPb(subscriptionInfo.toPb("project"))); + com.google.pubsub.v1.Subscription subscription = SUBSCRIPTION_INFO.toPb("project"); + subscriptionInfo = + SubscriptionInfo.fromPb(subscription.toBuilder().setTopic("_deleted-topic_").build()); + assertEquals(TopicId.deletedTopic(), subscriptionInfo.getTopic()); + assertEquals(NAME, subscriptionInfo.getName()); + assertEquals(PUSH_CONFIG, subscriptionInfo.getPushConfig()); + assertEquals(ACK_DEADLINE, subscriptionInfo.getAckDeadlineSeconds()); + } + + private void compareSubscriptionInfo(SubscriptionInfo expected, SubscriptionInfo value) { + assertEquals(expected, value); + assertEquals(expected.getTopic(), value.getTopic()); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.getPushConfig(), value.getPushConfig()); + assertEquals(expected.getAckDeadlineSeconds(), value.getAckDeadlineSeconds()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionTest.java new file mode 100644 index 000000000000..0e6ac4cc2b61 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/SubscriptionTest.java @@ -0,0 +1,436 @@ +/* + * 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.pubsub; + +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.PubSub.MessageConsumer; +import com.google.cloud.pubsub.PubSub.MessageProcessor; +import com.google.cloud.pubsub.PubSub.PullOption; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; + +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class SubscriptionTest { + + private static final TopicId TOPIC_ID = TopicId.of("project", "topic"); + private static final String NAME = "subscription"; + private static final String ENDPOINT = "https://example.com/push"; + private static final PushConfig PUSH_CONFIG = PushConfig.of(ENDPOINT); + private static final int ACK_DEADLINE = 42; + private static final SubscriptionInfo SUBSCRIPTION_INFO = + SubscriptionInfo.newBuilder(TOPIC_ID, NAME) + .setPushConfig(PUSH_CONFIG) + .setAckDeadLineSeconds(ACK_DEADLINE) + .build(); + private static final Message MESSAGE1 = Message.of("payload1"); + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE_PB1 = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE1.toPb()) + .setAckId("ackId1") + .build(); + private static final Message MESSAGE2 = Message.of("payload2"); + private static final com.google.pubsub.v1.ReceivedMessage MESSAGE_PB2 = + com.google.pubsub.v1.ReceivedMessage.newBuilder() + .setMessage(MESSAGE2.toPb()) + .setAckId("ackId2") + .build(); + private static final Policy POLICY = Policy.newBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + + private final PubSub serviceMockReturnsOptions = createStrictMock(PubSub.class); + private final PubSubOptions mockOptions = createStrictMock(PubSubOptions.class); + private PubSub pubsub; + private Subscription expectedSubscription; + private Subscription subscription; + + private void initializeExpectedSubscription(int optionsCalls) { + expect(serviceMockReturnsOptions.getOptions()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + pubsub = createStrictMock(PubSub.class); + expectedSubscription = new Subscription(serviceMockReturnsOptions, + new Subscription.BuilderImpl(SUBSCRIPTION_INFO)); + } + + private void initializeSubscription() { + subscription = new Subscription(pubsub, new Subscription.BuilderImpl(SUBSCRIPTION_INFO)); + } + + @After + public void tearDown() throws Exception { + verify(pubsub, serviceMockReturnsOptions); + } + + @Test + public void testBuilder() { + initializeExpectedSubscription(2); + replay(pubsub); + assertEquals(TOPIC_ID, expectedSubscription.getTopic()); + assertEquals(NAME, expectedSubscription.getName()); + assertEquals(PUSH_CONFIG, expectedSubscription.getPushConfig()); + assertEquals(ACK_DEADLINE, expectedSubscription.getAckDeadlineSeconds()); + assertSame(serviceMockReturnsOptions, expectedSubscription.getPubsub()); + Subscription builtSubscription = expectedSubscription.toBuilder() + .setName("newSubscription") + .setTopic("newProject", "newTopic") + .setPushConfig(null) + .setAckDeadLineSeconds(10) + .build(); + assertEquals(TopicId.of("newProject", "newTopic"), builtSubscription.getTopic()); + assertEquals("newSubscription", builtSubscription.getName()); + assertEquals(null, builtSubscription.getPushConfig()); + assertEquals(10, builtSubscription.getAckDeadlineSeconds()); + } + + @Test + public void testBuilderDeprecated() { + initializeExpectedSubscription(2); + replay(pubsub); + assertEquals(TOPIC_ID, expectedSubscription.topic()); + assertEquals(NAME, expectedSubscription.name()); + assertEquals(PUSH_CONFIG, expectedSubscription.pushConfig()); + assertEquals(ACK_DEADLINE, expectedSubscription.ackDeadlineSeconds()); + assertSame(serviceMockReturnsOptions, expectedSubscription.pubSub()); + Subscription builtSubscription = expectedSubscription.toBuilder() + .name("newSubscription") + .topic("newProject", "newTopic") + .pushConfig(null) + .ackDeadLineSeconds(10) + .build(); + assertEquals(TopicId.of("newProject", "newTopic"), builtSubscription.topic()); + assertEquals("newSubscription", builtSubscription.name()); + assertEquals(null, builtSubscription.pushConfig()); + assertEquals(10, builtSubscription.ackDeadlineSeconds()); + } + + @Test + public void testToBuilder() { + initializeExpectedSubscription(2); + replay(pubsub); + compareSubscription(expectedSubscription, expectedSubscription.toBuilder().build()); + } + + @Test + public void testReload() { + initializeExpectedSubscription(2); + SubscriptionInfo updatedInfo = SUBSCRIPTION_INFO.toBuilder().setName("newSubscription").build(); + Subscription expectedSubscription = + new Subscription(serviceMockReturnsOptions, new SubscriptionInfo.BuilderImpl(updatedInfo)); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscription(NAME)).andReturn(expectedSubscription); + replay(pubsub); + initializeSubscription(); + Subscription updatedSubscription = subscription.reload(); + compareSubscription(expectedSubscription, updatedSubscription); + } + + @Test + public void testReloadNull() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscription(NAME)).andReturn(null); + replay(pubsub); + initializeSubscription(); + assertNull(subscription.reload()); + } + + @Test + public void testReloadAsync() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(2); + SubscriptionInfo updatedInfo = SUBSCRIPTION_INFO.toBuilder().setName("newSubscription").build(); + Subscription expectedSubscription = + new Subscription(serviceMockReturnsOptions, new SubscriptionInfo.BuilderImpl(updatedInfo)); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscriptionAsync(NAME)) + .andReturn(Futures.immediateFuture(expectedSubscription)); + replay(pubsub); + initializeSubscription(); + Subscription updatedSubscription = subscription.reloadAsync().get(); + compareSubscription(expectedSubscription, updatedSubscription); + } + + @Test + public void testReloadAsyncNull() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscriptionAsync(NAME)) + .andReturn(Futures.immediateFuture(null)); + replay(pubsub); + initializeSubscription(); + assertNull(subscription.reloadAsync().get()); + } + + @Test + public void testDeleteTrue() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteSubscription(NAME)).andReturn(true); + replay(pubsub); + initializeSubscription(); + assertTrue(subscription.delete()); + } + + @Test + public void testDeleteFalse() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteSubscription(NAME)).andReturn(false); + replay(pubsub); + initializeSubscription(); + assertFalse(subscription.delete()); + } + + @Test + public void testDeleteAsyncTrue() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteSubscriptionAsync(NAME)) + .andReturn(Futures.immediateFuture(true)); + replay(pubsub); + initializeSubscription(); + assertTrue(subscription.deleteAsync().get()); + } + + @Test + public void testDeleteAsyncFalse() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteSubscriptionAsync(NAME)) + .andReturn(Futures.immediateFuture(false)); + replay(pubsub); + initializeSubscription(); + assertFalse(subscription.deleteAsync().get()); + } + + @Test + public void testReplacePushConfig() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + PushConfig pushConfig = PushConfig.of("https://example.com/newPush"); + pubsub.replacePushConfig(NAME, pushConfig); + EasyMock.expectLastCall(); + replay(pubsub); + initializeSubscription(); + subscription.replacePushConfig(pushConfig); + } + + @Test + public void testReplacePushConfig_Null() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + pubsub.replacePushConfig(NAME, null); + EasyMock.expectLastCall(); + replay(pubsub); + initializeSubscription(); + subscription.replacePushConfig(null); + } + + @Test + public void testReplacePushConfig_Async() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + PushConfig pushConfig = PushConfig.of("https://example.com/newPush"); + expect(pubsub.replacePushConfigAsync(NAME, pushConfig)) + .andReturn(Futures.immediateFuture(null)); + EasyMock.expectLastCall(); + replay(pubsub); + initializeSubscription(); + assertNull(subscription.replacePushConfigAsync(pushConfig).get()); + } + + @Test + public void testReplacePushConfigAsync_Null() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.replacePushConfigAsync(NAME, null)) + .andReturn(Futures.immediateFuture(null)); + replay(pubsub); + initializeSubscription(); + assertNull(subscription.replacePushConfigAsync(null).get()); + } + + @Test + public void testPull() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions).times(2); + replay(pubsub); + ReceivedMessage message1 = ReceivedMessage.fromPb(pubsub, NAME, MESSAGE_PB1); + ReceivedMessage message2 = ReceivedMessage.fromPb(pubsub, NAME, MESSAGE_PB2); + reset(pubsub); + expect(pubsub.getOptions()).andReturn(mockOptions); + List messages = ImmutableList.of(message1, message2); + expect(pubsub.pull(NAME, 42)).andReturn(messages.iterator()); + replay(pubsub); + initializeSubscription(); + assertEquals(messages, Lists.newArrayList(subscription.pull(42))); + } + + @Test + public void testPullAsync() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions).times(2); + replay(pubsub); + ReceivedMessage message1 = ReceivedMessage.fromPb(pubsub, NAME, MESSAGE_PB1); + ReceivedMessage message2 = ReceivedMessage.fromPb(pubsub, NAME, MESSAGE_PB2); + reset(pubsub); + expect(pubsub.getOptions()).andReturn(mockOptions); + List messages = ImmutableList.of(message1, message2); + expect(pubsub.pullAsync(NAME, 42)).andReturn(Futures.immediateFuture(messages.iterator())); + replay(pubsub); + initializeSubscription(); + assertEquals(messages, Lists.newArrayList(subscription.pullAsync(42).get())); + } + + @Test + public void testMessageConsumer() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + MessageConsumer messageConsumer = createStrictMock(MessageConsumer.class); + MessageProcessor messageProcessor = createStrictMock(MessageProcessor.class); + replay(messageConsumer, messageProcessor); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.pullAsync(NAME, messageProcessor)).andReturn(messageConsumer); + replay(pubsub); + initializeSubscription(); + assertSame(messageConsumer, subscription.pullAsync(messageProcessor)); + verify(messageConsumer, messageProcessor); + } + + @Test + public void testMessageConsumerWithOptions() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + MessageConsumer messageConsumer = createStrictMock(MessageConsumer.class); + MessageProcessor messageProcessor = createStrictMock(MessageProcessor.class); + replay(messageConsumer, messageProcessor); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.pullAsync(NAME, messageProcessor, PullOption.maxQueuedCallbacks(2))) + .andReturn(messageConsumer); + replay(pubsub); + initializeSubscription(); + assertSame(messageConsumer, + subscription.pullAsync(messageProcessor, PullOption.maxQueuedCallbacks(2))); + verify(messageConsumer, messageProcessor); + } + + @Test + public void testGetPolicy() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscriptionPolicy(NAME)).andReturn(POLICY); + replay(pubsub); + initializeSubscription(); + Policy policy = subscription.getPolicy(); + assertEquals(POLICY, policy); + } + + @Test + public void testGetPolicyNull() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscriptionPolicy(NAME)).andReturn(null); + replay(pubsub); + initializeSubscription(); + assertNull(subscription.getPolicy()); + } + + @Test + public void testGetPolicyAsync() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getSubscriptionPolicyAsync(NAME)).andReturn(Futures.immediateFuture(POLICY)); + replay(pubsub); + initializeSubscription(); + Policy policy = subscription.getPolicyAsync().get(); + assertEquals(POLICY, policy); + } + + @Test + public void testReplacePolicy() { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.replaceSubscriptionPolicy(NAME, POLICY)).andReturn(POLICY); + replay(pubsub); + initializeSubscription(); + Policy policy = subscription.replacePolicy(POLICY); + assertEquals(POLICY, policy); + } + + @Test + public void testReplacePolicyAsync() throws ExecutionException, InterruptedException { + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.replaceSubscriptionPolicyAsync(NAME, POLICY)) + .andReturn(Futures.immediateFuture(POLICY)); + replay(pubsub); + initializeSubscription(); + Policy policy = subscription.replacePolicyAsync(POLICY).get(); + assertEquals(POLICY, policy); + } + + @Test + public void testTestPermissions() { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + List permissionsResult = ImmutableList.of(true); + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.testSubscriptionPermissions(NAME, permissions)).andReturn(permissionsResult); + replay(pubsub); + initializeSubscription(); + assertEquals(permissionsResult, subscription.testPermissions(permissions)); + } + + @Test + public void testTestPermissionsAsync() throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.subscriptions.get"); + List permissionsResult = ImmutableList.of(true); + initializeExpectedSubscription(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.testSubscriptionPermissionsAsync(NAME, permissions)) + .andReturn(Futures.immediateFuture(permissionsResult)); + replay(pubsub); + initializeSubscription(); + assertEquals(permissionsResult, subscription.testPermissionsAsync(permissions).get()); + } + + private void compareSubscription(Subscription expected, Subscription value) { + assertEquals(expected, value); + assertEquals(expected.getTopic(), value.getTopic()); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.getPushConfig(), value.getPushConfig()); + assertEquals(expected.getAckDeadlineSeconds(), value.getAckDeadlineSeconds()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicIdTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicIdTest.java new file mode 100644 index 000000000000..72c9fc9c212e --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicIdTest.java @@ -0,0 +1,86 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class TopicIdTest { + + private static final String PROJECT = "project"; + private static final String NAME = "topic"; + private static final String TOPIC_PB = "projects/project/topics/topic"; + private static final String DELETED_TOPIC_NAME = "_deleted-topic_"; + + @Test + public void testOf() { + TopicId topicId = TopicId.of(PROJECT, NAME); + assertEquals(PROJECT, topicId.getProject()); + assertEquals(NAME, topicId.getTopic()); + topicId = TopicId.of(NAME); + assertNull(topicId.getProject()); + assertEquals(NAME, topicId.getTopic()); + assertFalse(topicId.isDeleted()); + } + + @Test + public void testOfDeprecated() { + TopicId topicId = TopicId.of(PROJECT, NAME); + assertEquals(PROJECT, topicId.project()); + assertEquals(NAME, topicId.topic()); + topicId = TopicId.of(NAME); + assertNull(topicId.project()); + assertEquals(NAME, topicId.topic()); + assertFalse(topicId.isDeleted()); + } + + @Test + public void testDeletedTopic() { + TopicId deletedTopic = TopicId.deletedTopic(); + assertNull(deletedTopic.getProject()); + assertEquals(DELETED_TOPIC_NAME, deletedTopic.getTopic()); + assertTrue(deletedTopic.isDeleted()); + assertSame(deletedTopic, TopicId.deletedTopic()); + } + + @Test + public void testToAndFromPb() { + TopicId topicId = TopicId.of(PROJECT, NAME); + String topicPb = topicId.toPb("otherProject"); + assertEquals(TOPIC_PB, topicPb); + compareTopicId(topicId, TopicId.fromPb(topicPb)); + topicId = TopicId.of(NAME); + topicPb = topicId.toPb("otherProject"); + assertEquals("projects/otherProject/topics/topic", topicPb); + compareTopicId(TopicId.of("otherProject", NAME), TopicId.fromPb(topicPb)); + assertSame(TopicId.deletedTopic(), TopicId.fromPb(DELETED_TOPIC_NAME)); + } + + private void compareTopicId(TopicId expected, TopicId value) { + assertEquals(expected, value); + assertEquals(expected.getProject(), value.getProject()); + assertEquals(expected.getTopic(), value.getTopic()); + assertEquals(expected.isDeleted(), value.isDeleted()); + assertEquals(expected.toPb("project"), value.toPb("project")); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicInfoTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicInfoTest.java new file mode 100644 index 000000000000..f89faed499c9 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicInfoTest.java @@ -0,0 +1,70 @@ +/* + * 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.pubsub; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class TopicInfoTest { + + private static final String NAME = "topic"; + private static final TopicInfo TOPIC_INFO = TopicInfo.newBuilder("topic").build(); + private static final TopicInfo DEPRECATED_TOPIC_INFO = TopicInfo.builder("topic").build(); + + @Test + public void testToBuilder() { + compareTopicInfo(TOPIC_INFO, TOPIC_INFO.toBuilder().build()); + TopicInfo topicInfo = TOPIC_INFO.toBuilder() + .setName("newTopic") + .build(); + assertEquals("newTopic", topicInfo.getName()); + topicInfo = topicInfo.toBuilder().setName(NAME).build(); + compareTopicInfo(TOPIC_INFO, topicInfo); + } + + @Test + public void testBuilder() { + assertEquals(NAME, TOPIC_INFO.getName()); + TopicInfo topicInfo = TopicInfo.newBuilder("wrongName").setName(NAME).build(); + compareTopicInfo(TOPIC_INFO, topicInfo); + } + + @Test + public void testBuilderDeprecated() { + assertEquals(NAME, DEPRECATED_TOPIC_INFO.name()); + TopicInfo topicInfo = TopicInfo.builder("wrongName").name(NAME).build(); + compareTopicInfo(TOPIC_INFO, topicInfo); + } + + @Test + public void testOf() { + compareTopicInfo(TOPIC_INFO, TopicInfo.of(NAME)); + } + + @Test + public void testToAndFromPb() { + compareTopicInfo(TOPIC_INFO, TopicInfo.fromPb(TOPIC_INFO.toPb("project"))); + assertEquals("projects/project/topics/topic", TOPIC_INFO.toPb("project").getName()); + } + + private void compareTopicInfo(TopicInfo expected, TopicInfo value) { + assertEquals(expected, value); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicTest.java new file mode 100644 index 000000000000..438759b73184 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/TopicTest.java @@ -0,0 +1,416 @@ +/* + * 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.pubsub; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.AsyncPage; +import com.google.cloud.AsyncPageImpl; +import com.google.cloud.Identity; +import com.google.cloud.Page; +import com.google.cloud.PageImpl; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.PubSub.ListOption; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; + +import org.junit.After; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class TopicTest { + + private static final String NAME = "topic"; + private static final TopicInfo TOPIC_INFO = TopicInfo.of(NAME); + private static final Policy POLICY = Policy.newBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build(); + + private final PubSub serviceMockReturnsOptions = createStrictMock(PubSub.class); + private final PubSubOptions mockOptions = createMock(PubSubOptions.class); + private PubSub pubsub; + private Topic expectedTopic; + private Topic topic; + + private void initializeExpectedTopic(int optionsCalls) { + expect(serviceMockReturnsOptions.getOptions()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + pubsub = createStrictMock(PubSub.class); + expectedTopic = new Topic(serviceMockReturnsOptions, new Topic.BuilderImpl(TOPIC_INFO)); + } + + private void initializeTopic() { + topic = new Topic(pubsub, new Topic.BuilderImpl(TOPIC_INFO)); + } + + @After + public void tearDown() throws Exception { + verify(pubsub, serviceMockReturnsOptions); + } + + @Test + public void testBuilder() { + initializeExpectedTopic(2); + replay(pubsub); + Topic builtTopic = expectedTopic.toBuilder().setName("newTopic").build(); + assertEquals("newTopic", builtTopic.getName()); + assertSame(serviceMockReturnsOptions, expectedTopic.getPubsub()); + } + + @Test + public void testBuilderDeprecated() { + initializeExpectedTopic(2); + replay(pubsub); + Topic builtTopic = expectedTopic.toBuilder().name("newTopic").build(); + assertEquals("newTopic", builtTopic.name()); + assertSame(serviceMockReturnsOptions, expectedTopic.pubSub()); + } + + @Test + public void testToBuilder() { + initializeExpectedTopic(2); + replay(pubsub); + compareTopic(expectedTopic, expectedTopic.toBuilder().build()); + } + + @Test + public void testReload() { + initializeExpectedTopic(2); + TopicInfo updatedInfo = TOPIC_INFO.toBuilder().setName("newTopic").build(); + Topic expectedTopic = + new Topic(serviceMockReturnsOptions, new TopicInfo.BuilderImpl(updatedInfo)); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopic(NAME)).andReturn(expectedTopic); + replay(pubsub); + initializeTopic(); + Topic updatedTopic = topic.reload(); + compareTopic(expectedTopic, updatedTopic); + } + + @Test + public void testReloadNull() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopic(NAME)).andReturn(null); + replay(pubsub); + initializeTopic(); + assertNull(topic.reload()); + } + + @Test + public void testReloadAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(2); + TopicInfo updatedInfo = TOPIC_INFO.toBuilder().setName("newTopic").build(); + Topic expectedTopic = + new Topic(serviceMockReturnsOptions, new TopicInfo.BuilderImpl(updatedInfo)); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopicAsync(NAME)) + .andReturn(Futures.immediateFuture(expectedTopic)); + replay(pubsub); + initializeTopic(); + Topic updatedTopic = topic.reloadAsync().get(); + compareTopic(expectedTopic, updatedTopic); + } + + @Test + public void testReloadAsyncNull() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopicAsync(NAME)).andReturn(Futures.immediateFuture(null)); + replay(pubsub); + initializeTopic(); + assertNull(topic.reloadAsync().get()); + } + + @Test + public void testDeleteTrue() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteTopic(NAME)).andReturn(true); + replay(pubsub); + initializeTopic(); + assertTrue(topic.delete()); + } + + @Test + public void testDeleteFalse() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteTopic(NAME)).andReturn(false); + replay(pubsub); + initializeTopic(); + assertFalse(topic.delete()); + } + + @Test + public void testDeleteAsyncTrue() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteTopicAsync(NAME)).andReturn(Futures.immediateFuture(true)); + replay(pubsub); + initializeTopic(); + assertTrue(topic.deleteAsync().get()); + } + + @Test + public void testDeleteAsyncFalse() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.deleteTopicAsync(NAME)).andReturn(Futures.immediateFuture(false)); + replay(pubsub); + initializeTopic(); + assertFalse(topic.deleteAsync().get()); + } + + @Test + public void testPublishOneMessage() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message = Message.of("payload1"); + String messageId = "messageId"; + expect(pubsub.publish(NAME, message)).andReturn(messageId); + replay(pubsub); + initializeTopic(); + assertEquals(messageId, topic.publish(message)); + } + + @Test + public void testPublishOneMessageAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message = Message.of("payload1"); + String messageId = "messageId"; + expect(pubsub.publishAsync(NAME, message)) + .andReturn(Futures.immediateFuture(messageId)); + replay(pubsub); + initializeTopic(); + assertEquals(messageId, topic.publishAsync(message).get()); + } + + @Test + public void testPublishMoreMessages() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + expect(pubsub.publish(NAME, message1, message2)).andReturn(messageIds); + replay(pubsub); + initializeTopic(); + assertEquals(messageIds, topic.publish(message1, message2)); + } + + @Test + public void testPublishMoreMessagesAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + expect(pubsub.publishAsync(NAME, message1, message2)) + .andReturn(Futures.immediateFuture(messageIds)); + replay(pubsub); + initializeTopic(); + assertEquals(messageIds, topic.publishAsync(message1, message2).get()); + } + + @Test + public void testPublishMessageList() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messages = ImmutableList.of(message1, message2); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + expect(pubsub.publish(NAME, messages)).andReturn(messageIds); + replay(pubsub); + initializeTopic(); + assertEquals(messageIds, topic.publish(messages)); + } + + @Test + public void testPublishMessageListAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + Message message1 = Message.of("payload1"); + Message message2 = Message.of("payload2"); + List messages = ImmutableList.of(message1, message2); + List messageIds = ImmutableList.of("messageId1", "messageId2"); + expect(pubsub.publishAsync(NAME, messages)) + .andReturn(Futures.immediateFuture(messageIds)); + replay(pubsub); + initializeTopic(); + assertEquals(messageIds, topic.publishAsync(messages).get()); + } + + @Test + public void testListSubscriptions() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + final List subscriptions = ImmutableList.of( + new SubscriptionId("project", "subscription1"), + new SubscriptionId("project", "subscription2")); + Page result = new PageImpl<>(null, null, subscriptions); + expect(pubsub.listSubscriptions(NAME)).andReturn(result); + replay(pubsub); + initializeTopic(); + assertEquals(subscriptions, topic.listSubscriptions().getValues()); + } + + @Test + public void testListSubscriptionsWithOptions() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + final List subscriptions = ImmutableList.of( + new SubscriptionId("project", "subscription1"), + new SubscriptionId("project", "subscription2")); + Page result = new PageImpl<>(null, null, subscriptions); + expect(pubsub.listSubscriptions(NAME, ListOption.pageSize(42))).andReturn(result); + replay(pubsub); + initializeTopic(); + assertEquals(subscriptions, topic.listSubscriptions(ListOption.pageSize(42)).getValues()); + } + + @Test + public void testListSubscriptionsAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + final List subscriptions = ImmutableList.of( + new SubscriptionId("project", "subscription1"), + new SubscriptionId("project", "subscription2")); + AsyncPage result = new AsyncPageImpl<>(null, null, subscriptions); + expect(pubsub.listSubscriptionsAsync(NAME)) + .andReturn(Futures.immediateFuture(result)); + replay(pubsub); + initializeTopic(); + assertEquals(subscriptions, topic.listSubscriptionsAsync().get().getValues()); + } + + @Test + public void testListSubscriptionsAsyncWithOptions() + throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + final List subscriptions = ImmutableList.of( + new SubscriptionId("project", "subscription1"), + new SubscriptionId("project", "subscription2")); + AsyncPage result = new AsyncPageImpl<>(null, null, subscriptions); + expect(pubsub.listSubscriptionsAsync(NAME, ListOption.pageSize(42))) + .andReturn(Futures.immediateFuture(result)); + replay(pubsub); + initializeTopic(); + assertEquals(subscriptions, + topic.listSubscriptionsAsync(ListOption.pageSize(42)).get().getValues()); + } + + @Test + public void testGetPolicy() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopicPolicy(NAME)).andReturn(POLICY); + replay(pubsub); + initializeTopic(); + Policy policy = topic.getPolicy(); + assertEquals(POLICY, policy); + } + + @Test + public void testGetPolicyNull() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopicPolicy(NAME)).andReturn(null); + replay(pubsub); + initializeTopic(); + assertNull(topic.getPolicy()); + } + + @Test + public void testGetPolicyAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.getTopicPolicyAsync(NAME)).andReturn(Futures.immediateFuture(POLICY)); + replay(pubsub); + initializeTopic(); + Policy policy = topic.getPolicyAsync().get(); + assertEquals(POLICY, policy); + } + + @Test + public void testReplacePolicy() { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.replaceTopicPolicy(NAME, POLICY)).andReturn(POLICY); + replay(pubsub); + initializeTopic(); + Policy policy = topic.replacePolicy(POLICY); + assertEquals(POLICY, policy); + } + + @Test + public void testReplacePolicyAsync() throws ExecutionException, InterruptedException { + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.replaceTopicPolicyAsync(NAME, POLICY)).andReturn(Futures.immediateFuture(POLICY)); + replay(pubsub); + initializeTopic(); + Policy policy = topic.replacePolicyAsync(POLICY).get(); + assertEquals(POLICY, policy); + } + + @Test + public void testTestPermissions() { + List permissions = ImmutableList.of("pubsub.topics.get"); + List permissionsResult = ImmutableList.of(true); + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.testTopicPermissions(NAME, permissions)).andReturn(permissionsResult); + replay(pubsub); + initializeTopic(); + assertEquals(permissionsResult, topic.testPermissions(permissions)); + } + + @Test + public void testTestPermissionsAsync() throws ExecutionException, InterruptedException { + List permissions = ImmutableList.of("pubsub.topics.get"); + List permissionsResult = ImmutableList.of(true); + initializeExpectedTopic(1); + expect(pubsub.getOptions()).andReturn(mockOptions); + expect(pubsub.testTopicPermissionsAsync(NAME, permissions)) + .andReturn(Futures.immediateFuture(permissionsResult)); + replay(pubsub); + initializeTopic(); + assertEquals(permissionsResult, topic.testPermissionsAsync(permissions).get()); + } + + private void compareTopic(Topic expected, Topic value) { + assertEquals(expected, value); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/it/ITPubSubTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/it/ITPubSubTest.java new file mode 100644 index 000000000000..9f9ccdecee25 --- /dev/null +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/it/ITPubSubTest.java @@ -0,0 +1,144 @@ +/* + * 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.pubsub.it; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; +import com.google.cloud.pubsub.BaseSystemTest; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.Subscription; +import com.google.cloud.pubsub.SubscriptionInfo; +import com.google.cloud.pubsub.Topic; +import com.google.cloud.pubsub.TopicInfo; +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ITPubSubTest extends BaseSystemTest { + + private static final PubSub PUB_SUB = PubSubOptions.getDefaultInstance().getService(); + private static final String NAME_SUFFIX = UUID.randomUUID().toString(); + + @Rule + public Timeout globalTimeout = Timeout.seconds(300); + + @Override + protected PubSub pubsub() { + return PUB_SUB; + } + + @Override + protected String formatForTest(String resourceName) { + return resourceName + "-" + NAME_SUFFIX; + } + + // Policy tests are defined here and not in BaseSystemTest because Pub/Sub emulator does not + // support IAM yet + + @Test + public void testTopicPolicy() { + String topicName = formatForTest("test-topic-policy"); + Topic topic = pubsub().create(TopicInfo.of(topicName)); + Policy policy = pubsub().getTopicPolicy(topicName); + policy = pubsub().replaceTopicPolicy(topicName, policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = + pubsub().testTopicPermissions(topicName, ImmutableList.of("pubsub.topics.get")); + assertTrue(permissions.get(0)); + topic.delete(); + } + + @Test + public void testNonExistingTopicPolicy() { + String topicName = formatForTest("test-non-existing-topic-policy"); + assertNull(pubsub().getTopicPolicy(topicName)); + } + + @Test + public void testTopicPolicyAsync() throws ExecutionException, InterruptedException { + String topicName = formatForTest("test-topic-policy-async"); + Topic topic = pubsub().create(TopicInfo.of(topicName)); + Policy policy = pubsub().getTopicPolicyAsync(topicName).get(); + policy = pubsub().replaceTopicPolicyAsync(topicName, policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()).get(); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = + pubsub().testTopicPermissionsAsync(topicName, ImmutableList.of("pubsub.topics.get")).get(); + assertTrue(permissions.get(0)); + topic.delete(); + } + + @Test + public void testSubscriptionPolicy() { + String topicName = formatForTest("test-subscription-policy"); + Topic topic = pubsub().create(TopicInfo.of(topicName)); + String subscriptionName = formatForTest("test-subscription-policy"); + Subscription subscription = pubsub().create(SubscriptionInfo.of(topicName, subscriptionName)); + Policy policy = pubsub().getSubscriptionPolicy(subscriptionName); + policy = pubsub().replaceSubscriptionPolicy(subscriptionName, policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = pubsub().testSubscriptionPermissions(subscriptionName, + ImmutableList.of("pubsub.subscriptions.get")); + assertTrue(permissions.get(0)); + topic.delete(); + subscription.delete(); + } + + @Test + public void testSubscriptionPolicyAsync() throws ExecutionException, InterruptedException { + String topicName = formatForTest("test-subscription-policy-async"); + Topic topic = pubsub().create(TopicInfo.of(topicName)); + String subscriptionName = formatForTest("test-subscription-policy-async"); + Subscription subscription = pubsub().create(SubscriptionInfo.of(topicName, subscriptionName)); + Policy policy = pubsub().getSubscriptionPolicyAsync(subscriptionName).get(); + policy = pubsub().replaceSubscriptionPolicyAsync(subscriptionName, policy.toBuilder() + .addIdentity(Role.viewer(), Identity.allAuthenticatedUsers()) + .build()).get(); + assertTrue(policy.getBindings().containsKey(Role.viewer())); + assertTrue(policy.getBindings().get(Role.viewer()).contains(Identity.allAuthenticatedUsers())); + List permissions = pubsub().testSubscriptionPermissionsAsync(subscriptionName, + ImmutableList.of("pubsub.subscriptions.get")).get(); + assertTrue(permissions.get(0)); + topic.delete(); + subscription.delete(); + } + + @Test + public void testNonExistingSubscriptionPolicy() { + String subscriptionName = formatForTest("test-non-existing-subscription-policy"); + assertNull(pubsub().getSubscriptionPolicy(subscriptionName)); + } +} diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/FakeClock.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/FakeClock.java index 90349ecaa52d..76bf3f8e570f 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/FakeClock.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/FakeClock.java @@ -20,15 +20,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -/** - * A Clock to help with testing time-based logic. - */ -class FakeClock extends Clock { +/** A Clock to help with testing time-based logic. */ +public class FakeClock extends Clock { private final AtomicLong millis = new AtomicLong(); // Advances the clock value by {@code time} in {@code timeUnit}. - void advance(long time, TimeUnit timeUnit) { + public void advance(long time, TimeUnit timeUnit) { millis.addAndGet(timeUnit.toMillis(time)); } diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/PublisherImplTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/PublisherImplTest.java index f0a14b6c3466..b01e7a4a3a6d 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/PublisherImplTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/spi/v1/PublisherImplTest.java @@ -23,14 +23,13 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; -import com.google.api.gax.bundling.FlowController; import com.google.api.gax.grpc.BundlingSettings; import com.google.api.gax.grpc.ChannelProvider; import com.google.api.gax.grpc.ExecutorProvider; import com.google.api.gax.grpc.FixedExecutorProvider; +import com.google.api.gax.grpc.FlowControlSettings; import com.google.api.gax.grpc.InstantiatingExecutorProvider; import com.google.cloud.pubsub.spi.v1.Publisher.Builder; -import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.ByteString; import com.google.pubsub.v1.PublishRequest; @@ -382,9 +381,9 @@ public void testPublisherGetters() throws Exception { .setElementCountThreshold(12) .build()); builder.setFlowControlSettings( - FlowController.Settings.newBuilder() - .setMaxOutstandingRequestBytes(Optional.of(13)) - .setMaxOutstandingElementCount(Optional.of(14)) + FlowControlSettings.newBuilder() + .setMaxOutstandingRequestBytes(13) + .setMaxOutstandingElementCount(14) .build()); Publisher publisher = builder.build(); @@ -392,10 +391,8 @@ public void testPublisherGetters() throws Exception { assertEquals(10, (long) publisher.getBundlingSettings().getRequestByteThreshold()); assertEquals(new Duration(11), publisher.getBundlingSettings().getDelayThreshold()); assertEquals(12, (long) publisher.getBundlingSettings().getElementCountThreshold()); - assertEquals( - Optional.of(13), publisher.getFlowControlSettings().getMaxOutstandingRequestBytes()); - assertEquals( - Optional.of(14), publisher.getFlowControlSettings().getMaxOutstandingElementCount()); + assertEquals(13, (long) publisher.getFlowControlSettings().getMaxOutstandingRequestBytes()); + assertEquals(14, (long) publisher.getFlowControlSettings().getMaxOutstandingElementCount()); assertTrue(publisher.failOnFlowControlLimits()); publisher.shutdown(); } @@ -414,7 +411,7 @@ public void testBuilderParametersAndDefaults() { assertEquals( Publisher.Builder.DEFAULT_ELEMENT_COUNT_THRESHOLD, builder.bundlingSettings.getElementCountThreshold().longValue()); - assertEquals(FlowController.Settings.DEFAULT, builder.flowControlSettings); + assertEquals(FlowControlSettings.getDefaultInstance(), builder.flowControlSettings); assertEquals(Publisher.Builder.DEFAULT_RETRY_SETTINGS, builder.retrySettings); } @@ -526,15 +523,15 @@ public void testBuilderInvalidArguments() { } builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingRequestBytes(Optional.of(1)) + .setMaxOutstandingRequestBytes(1) .build()); try { builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingRequestBytes(Optional.of(0)) + .setMaxOutstandingRequestBytes(0) .build()); fail("Should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException expected) { @@ -542,9 +539,9 @@ public void testBuilderInvalidArguments() { } try { builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingRequestBytes(Optional.of(-1)) + .setMaxOutstandingRequestBytes(-1) .build()); fail("Should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException expected) { @@ -552,15 +549,15 @@ public void testBuilderInvalidArguments() { } builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingElementCount(Optional.of(1)) + .setMaxOutstandingElementCount(1) .build()); try { builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingElementCount(Optional.of(0)) + .setMaxOutstandingElementCount(0) .build()); fail("Should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException expected) { @@ -568,9 +565,9 @@ public void testBuilderInvalidArguments() { } try { builder.setFlowControlSettings( - FlowController.Settings.DEFAULT + FlowControlSettings.getDefaultInstance() .toBuilder() - .setMaxOutstandingElementCount(Optional.of(-1)) + .setMaxOutstandingElementCount(-1) .build()); fail("Should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException expected) { diff --git a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/testing/LocalPubSubHelperTest.java b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/testing/LocalPubSubHelperTest.java index 2e1cc0cbd0dc..143f646b5a2e 100644 --- a/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/testing/LocalPubSubHelperTest.java +++ b/google-cloud-pubsub/src/test/java/com/google/cloud/pubsub/testing/LocalPubSubHelperTest.java @@ -22,6 +22,10 @@ import static org.junit.Assert.assertTrue; import com.google.cloud.NoCredentials; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubException; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.TopicInfo; import org.joda.time.Duration; import org.junit.Rule; @@ -45,11 +49,26 @@ public void testCreate() { assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX)); } + @Test + public void testOptions() { + LocalPubSubHelper helper = LocalPubSubHelper.create(); + PubSubOptions options = helper.getOptions(); + assertTrue(options.getProjectId().startsWith(PROJECT_ID_PREFIX)); + assertTrue(options.getHost().startsWith("localhost:")); + assertSame(NoCredentials.getInstance(), options.getCredentials()); + } + @Test public void testStartStopReset() throws IOException, InterruptedException, TimeoutException { LocalPubSubHelper helper = LocalPubSubHelper.create(); helper.start(); + PubSub pubsub = helper.getOptions().getService(); + pubsub.create(TopicInfo.of(TOPIC)); + assertNotNull(pubsub.getTopic(TOPIC)); helper.reset(); + assertNull(pubsub.getTopic(TOPIC)); helper.stop(Duration.standardMinutes(1)); + thrown.expect(PubSubException.class); + pubsub.getTopic(TOPIC); } } diff --git a/google-cloud-resourcemanager/README.md b/google-cloud-resourcemanager/README.md index e261feb07158..fd3e188a3e4f 100644 --- a/google-cloud-resourcemanager/README.md +++ b/google-cloud-resourcemanager/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-resourcemanager - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-resourcemanager:0.8.0' +compile 'com.google.cloud:google-cloud-resourcemanager:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-resourcemanager" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-resourcemanager" % "0.8.2-alpha" ``` Example Application diff --git a/google-cloud-resourcemanager/pom.xml b/google-cloud-resourcemanager/pom.xml index cc0da8a2cb30..29e1625a3ee9 100644 --- a/google-cloud-resourcemanager/pom.xml +++ b/google-cloud-resourcemanager/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-resourcemanager diff --git a/google-cloud-speech/pom.xml b/google-cloud-speech/pom.xml index 52fd58a97b58..4d69d86b70cc 100644 --- a/google-cloud-speech/pom.xml +++ b/google-cloud-speech/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-speech @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-speech-v1beta1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-storage/README.md b/google-cloud-storage/README.md index d415267d7a0e..1c6a22155c6e 100644 --- a/google-cloud-storage/README.md +++ b/google-cloud-storage/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-storage - 0.8.0-beta + 0.8.2-beta ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-storage:0.8.0-beta' +compile 'com.google.cloud:google-cloud-storage:0.8.2-beta' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "0.8.0-beta" +libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "0.8.2-beta" ``` Example Application diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 1b7c8e86be6b..a4e030c60944 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -12,7 +12,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-storage diff --git a/google-cloud-trace/pom.xml b/google-cloud-trace/pom.xml index f787b6c3924b..954d9b201edb 100644 --- a/google-cloud-trace/pom.xml +++ b/google-cloud-trace/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-trace @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-trace-v1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud-translate/README.md b/google-cloud-translate/README.md index 5571619bb0c2..cab062532488 100644 --- a/google-cloud-translate/README.md +++ b/google-cloud-translate/README.md @@ -22,16 +22,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud-translate - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud-translate:0.8.0' +compile 'com.google.cloud:google-cloud-translate:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-translate" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud-translate" % "0.8.2-alpha" ``` Example Application diff --git a/google-cloud-translate/pom.xml b/google-cloud-translate/pom.xml index 8d645b08643e..aac78a6e1215 100644 --- a/google-cloud-translate/pom.xml +++ b/google-cloud-translate/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-translate diff --git a/google-cloud-vision/pom.xml b/google-cloud-vision/pom.xml index f14e51c7b2e8..11ed894b6a53 100644 --- a/google-cloud-vision/pom.xml +++ b/google-cloud-vision/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha google-cloud-vision @@ -30,7 +30,7 @@ com.google.api.grpc grpc-google-cloud-vision-v1 - 0.1.3 + 0.1.5 io.grpc diff --git a/google-cloud/README.md b/google-cloud/README.md index 9da900cd8b2a..1584a288e364 100644 --- a/google-cloud/README.md +++ b/google-cloud/README.md @@ -27,16 +27,16 @@ If you are using Maven, add this to your pom.xml file com.google.cloud google-cloud - 0.8.0 + 0.8.2-alpha ``` If you are using Gradle, add this to your dependencies ```Groovy -compile 'com.google.cloud:google-cloud:0.8.0' +compile 'com.google.cloud:google-cloud:0.8.2-alpha' ``` If you are using SBT, add this to your dependencies ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud" % "0.8.0" +libraryDependencies += "com.google.cloud" % "google-cloud" % "0.8.2-alpha" ``` Troubleshooting diff --git a/google-cloud/pom.xml b/google-cloud/pom.xml index a162e2e61f7f..42e13b2f6207 100644 --- a/google-cloud/pom.xml +++ b/google-cloud/pom.xml @@ -11,7 +11,7 @@ com.google.cloud google-cloud-pom - 0.8.1-SNAPSHOT + 0.8.2-alpha diff --git a/pom.xml b/pom.xml index 8534ae252605..e6538721c1f5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-pom pom - 0.8.1-SNAPSHOT + 0.8.2-alpha Google Cloud https://github.com/GoogleCloudPlatform/google-cloud-java @@ -91,9 +91,9 @@ UTF-8 github 0.6.0 - 1.0.1 - 0.8.1-SNAPSHOT - 0.8.1-beta-SNAPSHOT + 1.0.3 + 0.8.2-alpha + 0.8.2-beta ${beta.version} google-cloud diff --git a/utilities/verify.sh b/utilities/verify.sh index d86239c5d6cb..81ed99061289 100755 --- a/utilities/verify.sh +++ b/utilities/verify.sh @@ -10,7 +10,7 @@ if [ "${TRAVIS_PULL_REQUEST}" == "false" ]; then chmod 700 $TRAVIS_BUILD_DIR/signing-tools tar xvf $TRAVIS_BUILD_DIR/signing-tools.tar -C $TRAVIS_BUILD_DIR/signing-tools # Run verify - mvn verify -Djava.util.logging.config.file=logging.properties -P release + mvn verify --quiet -Djava.util.logging.config.file=logging.properties -P release else - mvn verify -Djava.util.logging.config.file=logging.properties -DskipITs -P release + mvn verify --quiet -Djava.util.logging.config.file=logging.properties -DskipITs -P release fi