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