├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── spotify │ └── google │ └── cloud │ └── pubsub │ └── client │ ├── Acker.java │ ├── AcknowledgeRequest.java │ ├── Backoff.java │ ├── ConfigurableSSLSocketFactory.java │ ├── Json.java │ ├── Message.java │ ├── ModifyAckDeadlineRequest.java │ ├── PublishRequest.java │ ├── PublishResponse.java │ ├── Publisher.java │ ├── Pubsub.java │ ├── PubsubException.java │ ├── PubsubFuture.java │ ├── PullRequest.java │ ├── PullResponse.java │ ├── Puller.java │ ├── PushConfig.java │ ├── QueueFullException.java │ ├── ReceivedMessage.java │ ├── RequestFailedException.java │ ├── RequestInfo.java │ ├── Subscription.java │ ├── SubscriptionCreateRequest.java │ ├── SubscriptionList.java │ ├── Topic.java │ └── TopicList.java └── test ├── java └── com │ └── spotify │ └── google │ └── cloud │ └── pubsub │ └── client │ ├── AckerTest.java │ ├── AssertWithTimeout.java │ ├── BackoffTest.java │ ├── JsonTest.java │ ├── MessageTest.java │ ├── PublishRequestTest.java │ ├── PublisherFailureTest.java │ ├── PublisherTest.java │ ├── PubsubFutureTest.java │ ├── PubsubTest.java │ ├── PullResponseTest.java │ ├── PullerTest.java │ ├── PushConfigTest.java │ ├── ReceivedMessageTest.java │ ├── SubscriptionTest.java │ ├── TopicTest.java │ ├── example │ ├── PublisherExample.java │ ├── PubsubExample.java │ └── PullerExample.java │ └── integration │ ├── EndToEndBenchmark.java │ ├── GoogleClientPullBenchmark.java │ ├── ProgressMeter.java │ ├── PublisherBenchmark.java │ ├── PublisherIT.java │ ├── PubsubIT.java │ ├── PullerBenchmark.java │ └── Util.java └── resources └── logback-test.xml /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up JDK 17 12 | uses: actions/setup-java@v3 13 | with: 14 | java-version: '17' 15 | distribution: 'temurin' 16 | - name: Build with Maven 17 | run: mvn --batch-mode --update-snapshots package 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | credentials.json 2 | target/ 3 | .idea/ 4 | *.iml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | install: true 7 | 8 | script: mvn clean test 9 | 10 | after_success: 11 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2012 Spotify AB 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | async-google-pubsub-client 2 | Copyright 2011-2015 Spotify AB 3 | 4 | This product includes software developed at 5 | Spotify AB (http://www.spotify.com/). 6 | 7 | This project uses google-api-client from Google, which is licensed with Apache Licence 2.0. 8 | 9 | This project uses google-http-client from Google, which is licensed with Apache Licence 2.0. 10 | 11 | This project uses guava from Google, which is licensed with Apache Licence 2.0. 12 | 13 | This project uses vert.x which is licensed with Apache Licence 2.0. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE: This library is sunsetting. Please consider using the official [Google Cloud Java Client for Pub/Sub](https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-clients/google-cloud-pubsub).** 2 | 3 | async-google-pubsub-client 4 | ========================== 5 | 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.spotify/async-google-pubsub-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.spotify/async-google-pubsub-client) [![Build Status](https://travis-ci.org/spotify/async-google-pubsub-client.svg?branch=master)](https://travis-ci.org/spotify/async-google-pubsub-client) [![codecov.io](http://codecov.io/github/spotify/async-google-pubsub-client/coverage.svg?branch=master)](http://codecov.io/github/spotify/async-google-pubsub-client?branch=master) 7 | 8 | A performant [Google Cloud Pub/Sub](https://cloud.google.com/pubsub/) client and batch publisher. 9 | 10 | What 11 | ---- 12 | 13 | A low level Pub/Sub client and a concurrent per-topic batching Publisher. 14 | 15 | The client uses async-http-client with the Netty provider for making efficient and async HTTP requests to the Google Cloud Pub/Sub api. 16 | 17 | The publisher is implemented on top of the async Pub/Sub client and concurrently gathers individual messages into per-topic batches which are then pushed to Google Cloud Pub/Sub at a specified desired request concurrency level in order to achieve both low-latency and high throughput. 18 | 19 | Why 20 | --- 21 | The official Google Cloud Pub/Sub client library was not performant enough for our purposes due to blocking I/O etc. 22 | 23 | Usage 24 | ----- 25 | 26 | ### Pubsub Client 27 | 28 | ```java 29 | // Create a topic 30 | pubsub.createTopic("my-google-cloud-project", "the-topic").get(); 31 | 32 | // Create a subscription 33 | pubsub.createSubscription("my-google-cloud-project", "the-subscription-name", "the-topic").get(); 34 | 35 | // Create a batch of messages 36 | final List messages = asList( 37 | Message.builder() 38 | .attributes("type", "foo") 39 | .data(encode("hello foo")) 40 | .build(), 41 | Message.builder() 42 | .attributes("type", "bar") 43 | .data(encode("hello foo")) 44 | .build()); 45 | 46 | // Publish the messages 47 | final List messageIds = pubsub.publish("my-google-cloud-project", "the-topic", messages).get(); 48 | System.out.println("Message IDs: " + messageIds); 49 | 50 | // Pull the message 51 | final List received = pubsub.pull("my-google-cloud-project", "the-subscription").get(); 52 | System.out.println("Received Messages: " + received); 53 | 54 | // Ack the received messages 55 | final List ackIds = received.stream().map(ReceivedMessage::ackId).collect(Collectors.toList()); 56 | pubsub.acknowledge("my-google-cloud-project", "the-subscription", ackIds).get(); 57 | ``` 58 | 59 | ### Publisher 60 | 61 | ```java 62 | final Pubsub pubsub = Pubsub.builder() 63 | .build(); 64 | 65 | final Publisher publisher = Publisher.builder() 66 | .pubsub(pubsub) 67 | .project("my-google-cloud-project") 68 | .concurrency(128) 69 | .build(); 70 | 71 | // A never ending stream of messages... 72 | final Iterable messageStream = incomingMessages(); 73 | 74 | // Publish incoming messages 75 | messageStream.forEach(m -> publisher.publish(m.topic, m.message)); 76 | ``` 77 | 78 | ### Puller 79 | 80 | ```java 81 | final Pubsub pubsub = Pubsub.builder() 82 | .build(); 83 | 84 | final MessageHandler handler = (puller, subscription, message, ackId) -> { 85 | System.out.println("got message: " + message); 86 | return CompletableFuture.completedFuture(ackId); 87 | }; 88 | 89 | final Puller puller = builder() 90 | .pubsub(pubsub) 91 | .project("my-google-cloud-project") 92 | .subscription("my-subscription") 93 | .concurrency(32) 94 | .messageHandler(handler) 95 | .build(); 96 | ``` 97 | 98 | ### `pom.xml` 99 | 100 | ```xml 101 | 102 | com.spotify 103 | async-google-pubsub-client 104 | 1.31 105 | 106 | ``` 107 | 108 | 109 | Publisher Benchmark 110 | ------------------- 111 | 112 | Note: This benchmark uses a lot of quota and network bandwidth. 113 | 114 | ``` 115 | $ mvn exec:exec -Dexec.executable="java" -Dexec.classpathScope="test" -Dexec.args="-cp %classpath com.spotify.google.cloud.pubsub.client.integration.PublisherBenchmark" 116 | [INFO] Scanning for projects... 117 | [INFO] Inspecting build with total of 1 modules... 118 | [INFO] Installing Nexus Staging features: 119 | [INFO] ... total of 1 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin 120 | [INFO] 121 | [INFO] ------------------------------------------------------------------------ 122 | [INFO] Building async-google-pubsub-client 1.13-SNAPSHOT 123 | [INFO] ------------------------------------------------------------------------ 124 | [INFO] 125 | [INFO] --- exec-maven-plugin:1.4.0:exec (default-cli) @ async-google-pubsub-client --- 126 | 2015-09-29 18:31:44 (1 s) 127 | ---------------------------------------------------------------------------------------------------------------- 128 | publishes 1,235 ( 1,235 avg) messages/s 934.888 ( 934.888 avg) ms latency 1,237 total 129 | 130 | ... warmup ... 131 | 132 | 2015-09-29 18:31:53 (10 s) 133 | ---------------------------------------------------------------------------------------------------------------- 134 | publishes 198,902 ( 156,137 avg) messages/s 503.912 ( 620.260 avg) ms latency 1,565,391 total 135 | 136 | 2015-09-29 18:31:54 (11 s) 137 | ---------------------------------------------------------------------------------------------------------------- 138 | publishes 212,755 ( 177,264 avg) messages/s 475.023 ( 602.638 avg) ms latency 1,778,331 total 139 | 140 | ... 141 | ``` 142 | 143 | End To End Benchmark 144 | ------------------- 145 | 146 | Note: This benchmark uses a lot of quota and network bandwidth. 147 | 148 | ``` 149 | $ mvn exec:exec -Dexec.executable="java" -Dexec.classpathScope="test" -Dexec.args="-cp %classpath com.spotify.google.cloud.pubsub.client.integration.EndToEndBenchmark" 150 | [INFO] Scanning for projects... 151 | [INFO] Inspecting build with total of 1 modules... 152 | [INFO] Installing Nexus Staging features: 153 | [INFO] ... total of 1 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin 154 | [INFO] 155 | [INFO] ------------------------------------------------------------------------ 156 | [INFO] Building async-google-pubsub-client 1.13-SNAPSHOT 157 | [INFO] ------------------------------------------------------------------------ 158 | [INFO] 159 | [INFO] --- exec-maven-plugin:1.4.0:exec (default-cli) @ async-google-pubsub-client --- 160 | 2015-09-29 18:29:12 (1 s) 161 | ---------------------------------------------------------------------------------------------------------------- 162 | publishes 15,230 ( 15,230 avg) messages/s 650.532 ( 650.532 avg) ms latency 15,224 total 163 | receives 0 ( 0 avg) messages/s 0.000 ( 0.000 avg) ms latency 0 total 164 | 165 | ... warmup ... 166 | 167 | 2015-09-29 18:29:27 (16 s) 168 | ---------------------------------------------------------------------------------------------------------------- 169 | publishes 85,455 ( 79,706 avg) messages/s 588.480 ( 659.066 avg) ms latency 980,385 total 170 | receives 78,382 ( 81,186 avg) messages/s 1,112.738 ( 1,319.294 avg) ms latency 939,036 total 171 | 172 | 2015-09-29 18:29:28 (17 s) 173 | ---------------------------------------------------------------------------------------------------------------- 174 | publishes 107,998 ( 84,596 avg) messages/s 597.299 ( 645.375 avg) ms latency 1,088,490 total 175 | receives 103,196 ( 83,902 avg) messages/s 1,071.667 ( 1,212.498 avg) ms latency 1,042,383 total 176 | ... 177 | ``` 178 | 179 | Pulling Benchmark 180 | ------------------- 181 | 182 | Note: This benchmark uses a lot of quota and network bandwidth. 183 | 184 | Set the `GOOGLE_PUBSUB_SUBSCRIPTION` env var to the name of a subscription to consume from. 185 | 186 | ``` 187 | $ mvn exec:exec -Dexec.executable="java" -Dexec.classpathScope="test" -Dexec.args="-cp %classpath com.spotify.google.cloud.pubsub.client.integration.PullerBenchmark" 188 | ``` 189 | 190 | Releasing 191 | --------- 192 | 193 | We tag releases on github and publish release jars to maven central hosted by 194 | Sonatype: 195 | 196 | ### Prerequisites 197 | 198 | 199 | 1. Sonatype credentials for publishing to maven central. Apply for permission 200 | to publish jars on the `com.spotify` group id. 201 | See . 202 | 203 | 2. Add the sonatype credentials to `~/.m2/settings.xml` 204 | 205 | 206 | ossrh 207 | YOUR_SONATYPE_USER 208 | YOUR_SONATYPE_PASS 209 | 210 | 211 | 3. Set up GnuPG. See . 212 | Make sure that you've distributed your public key to a key server. 213 | 214 | 215 | ### Performing a Release 216 | 217 | Have your GnuPG password ready. Both prepare and perform steps will ask you for it. 218 | 219 | *Note:* The current tests run during both `prepare` and `perform` include 220 | integration tests against the real Google Pub/Sub API. Verify 221 | that you have a suitable default project and credentials 222 | configured with the `gcloud` cli. 223 | 224 | 1. Tag and push a new release to github: 225 | 226 | mvn release:prepare 227 | 228 | 2. Publish the signed jar to maven central: 229 | 230 | mvn release:perform 231 | 232 | 233 | Todo 234 | ---- 235 | * Implement a high level consumer (raw pull/ack support is there) 236 | * Implement retries on auth failure 237 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.spotify 5 | async-google-pubsub-client 6 | 1.35-SNAPSHOT 7 | jar 8 | 9 | async-google-pubsub-client 10 | https://github.com/spotify/async-google-pubsub-client 11 | 12 | 13 | com.spotify 14 | foss-root 15 | 15 16 | 17 | 18 | 19 | 20 | Apache License, Version 2.0 21 | http://www.apache.org/licenses/LICENSE-2.0.txt 22 | repo 23 | 24 | 25 | 26 | 27 | scm:git:https://github.com/spotify/async-google-pubsub-client.git 28 | scm:git:git@github.com:spotify/async-google-pubsub-client.git 29 | https://github.com/spotify/async-google-pubsub-client 30 | HEAD 31 | 32 | 33 | 34 | 35 | danielnorberg 36 | dano@spotify.com 37 | Daniel Norberg 38 | 39 | 40 | 41 | 42 | UTF-8 43 | 8 44 | 45 | 46 | 47 | 48 | 49 | com.fasterxml.jackson 50 | jackson-bom 51 | 2.13.4.20221013 52 | import 53 | pom 54 | 55 | 56 | com.google.cloud 57 | libraries-bom 58 | 26.1.3 59 | import 60 | pom 61 | 62 | 63 | io.norberg 64 | auto-matter-bom 65 | 0.26.0 66 | import 67 | pom 68 | 69 | 70 | 71 | 72 | 73 | 74 | com.google.oauth-client 75 | google-oauth-client 76 | 77 | 78 | com.google.api-client 79 | google-api-client 80 | 81 | 82 | com.google.guava 83 | guava-jdk5 84 | 85 | 86 | 87 | 88 | org.slf4j 89 | slf4j-api 90 | 1.7.36 91 | 92 | 93 | com.google.guava 94 | guava 95 | 96 | 97 | io.norberg 98 | auto-matter 99 | provided 100 | 101 | 102 | io.norberg 103 | auto-matter-jackson 104 | 105 | 106 | com.fasterxml.jackson.core 107 | jackson-databind 108 | 109 | 110 | com.fasterxml.jackson.core 111 | jackson-core 112 | 113 | 114 | com.fasterxml.jackson.datatype 115 | jackson-datatype-jdk8 116 | 117 | 118 | com.fasterxml.jackson.datatype 119 | jackson-datatype-guava 120 | 121 | 122 | com.fasterxml.jackson.datatype 123 | jackson-datatype-jsr310 124 | 125 | 126 | com.ning 127 | async-http-client 128 | 1.9.40 129 | 130 | 131 | io.netty 132 | netty 133 | 3.10.6.Final 134 | 135 | 136 | com.swrve 137 | rate-limited-logger 138 | 2.0.2 139 | 140 | 141 | com.google.code.findbugs 142 | jsr305 143 | 144 | 145 | 146 | 147 | 148 | 149 | junit 150 | junit 151 | 4.13.2 152 | test 153 | 154 | 155 | ch.qos.logback 156 | logback-classic 157 | 1.2.11 158 | test 159 | 160 | 161 | org.hamcrest 162 | hamcrest-library 163 | 1.3 164 | test 165 | 166 | 167 | com.spotify 168 | logging 169 | 2.3.4 170 | test 171 | 172 | 173 | com.google.apis 174 | google-api-services-pubsub 175 | v1-rev20221020-2.0.0 176 | test 177 | 178 | 179 | com.squareup.okhttp 180 | mockwebserver 181 | 2.7.5 182 | test 183 | 184 | 185 | org.mockito 186 | mockito-core 187 | 5.0.0 188 | test 189 | 190 | 191 | org.awaitility 192 | awaitility 193 | 4.2.0 194 | test 195 | 196 | 197 | 198 | 199 | 200 | 201 | maven-compiler-plugin 202 | 203 | false 204 | true 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-enforcer-plugin 210 | 211 | 212 | 213 | 214 | 215 | 216 | org.jacoco 217 | jacoco-maven-plugin 218 | 0.8.8 219 | 220 | 221 | 222 | prepare-agent 223 | 224 | 225 | 226 | report 227 | test 228 | 229 | report 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Acker.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.util.concurrent.MoreExecutors; 40 | 41 | import java.io.Closeable; 42 | import java.io.IOException; 43 | import java.util.ArrayList; 44 | import java.util.List; 45 | import java.util.Objects; 46 | import java.util.Optional; 47 | import java.util.concurrent.CompletableFuture; 48 | import java.util.concurrent.ConcurrentLinkedQueue; 49 | import java.util.concurrent.RejectedExecutionException; 50 | import java.util.concurrent.ScheduledExecutorService; 51 | import java.util.concurrent.ScheduledThreadPoolExecutor; 52 | import java.util.concurrent.atomic.AtomicBoolean; 53 | import java.util.concurrent.atomic.AtomicInteger; 54 | 55 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 56 | import static java.util.concurrent.TimeUnit.SECONDS; 57 | 58 | public class Acker implements Closeable { 59 | 60 | private final ScheduledExecutorService scheduler = 61 | MoreExecutors.getExitingScheduledExecutorService(new ScheduledThreadPoolExecutor(1)); 62 | 63 | private final AtomicInteger size = new AtomicInteger(); 64 | private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); 65 | private final AtomicBoolean scheduled = new AtomicBoolean(); 66 | private final AtomicInteger outstanding = new AtomicInteger(); 67 | private final AtomicBoolean sending = new AtomicBoolean(); 68 | 69 | private final Pubsub pubsub; 70 | private final String project; 71 | private final String subscription; 72 | private final int batchSize; 73 | private final int queueSize; 74 | private final long maxLatencyMs; 75 | private final int concurrency; 76 | private final Backoff backoff; 77 | 78 | private Acker(final Builder builder) { 79 | this.pubsub = Objects.requireNonNull(builder.pubsub, "pubsub"); 80 | this.project = Objects.requireNonNull(builder.project, "project"); 81 | this.subscription = Objects.requireNonNull(builder.subscription, "subscription"); 82 | this.batchSize = builder.batchSize; 83 | this.queueSize = Optional.ofNullable(builder.queueSize).orElseGet(() -> batchSize * 10); 84 | this.maxLatencyMs = builder.maxLatencyMs; 85 | this.concurrency = builder.concurrency; 86 | 87 | this.backoff = Backoff.builder() 88 | .initialInterval(builder.maxLatencyMs) 89 | .maxBackoffMultiplier(builder.maxBackoffMultiplier) 90 | .build(); 91 | } 92 | 93 | public CompletableFuture acknowledge(final String ackId) { 94 | final CompletableFuture future = new CompletableFuture<>(); 95 | 96 | // Enforce queue size limit 97 | int currentSize; 98 | int newSize; 99 | do { 100 | currentSize = size.get(); 101 | newSize = currentSize + 1; 102 | if (newSize > queueSize) { 103 | future.completeExceptionally(new QueueFullException()); 104 | return future; 105 | } 106 | } while (!size.compareAndSet(currentSize, newSize)); 107 | 108 | // Enqueue outgoing ack 109 | queue.add(new QueuedAck(ackId, future)); 110 | 111 | // Reached the batch size? Send immediately. 112 | if (newSize >= batchSize) { 113 | send(); 114 | return future; 115 | } 116 | 117 | // Schedule later acking, allowing more acks to gather into a larger batch. 118 | if (scheduled.compareAndSet(false, true)) { 119 | try { 120 | scheduler.schedule(this::scheduledSend, maxLatencyMs, MILLISECONDS); 121 | } catch (RejectedExecutionException ignore) { 122 | // Race with a call to close(). Ignore. 123 | } 124 | } 125 | 126 | return future; 127 | } 128 | 129 | private void scheduledSend() { 130 | scheduled.set(false); 131 | send(); 132 | } 133 | 134 | private void send() { 135 | if (sending.compareAndSet(false, true)) { 136 | try { 137 | // Drain queue 138 | while (size.get() > 0 && outstanding.get() < concurrency) { 139 | final int sent = sendBatch(); 140 | if (sent == 0) { 141 | return; 142 | } 143 | } 144 | } finally { 145 | sending.set(false); 146 | } 147 | } 148 | } 149 | 150 | private int sendBatch() { 151 | final List batch = new ArrayList<>(); 152 | final List> futures = new ArrayList<>(); 153 | 154 | // Drain queue up to batch size 155 | while (batch.size() < batchSize) { 156 | final QueuedAck ack = queue.poll(); 157 | if (ack == null) { 158 | break; 159 | } 160 | batch.add(ack.ackId); 161 | futures.add(ack.future); 162 | } 163 | 164 | // Was there anything to send? 165 | if (batch.size() == 0) { 166 | return 0; 167 | } 168 | 169 | // Decrement the queue size counter 170 | size.updateAndGet(i -> i - batch.size()); 171 | 172 | // Send the batch request and increment the outstanding request counter 173 | outstanding.incrementAndGet(); 174 | final PubsubFuture batchFuture = pubsub.acknowledge(project, subscription, batch); 175 | batchFuture.whenComplete( 176 | (Void ignore, Throwable ex) -> { 177 | 178 | // Decrement the outstanding request counter 179 | outstanding.decrementAndGet(); 180 | 181 | // Fail all futures if the batch request failed 182 | if (ex != null) { 183 | futures.forEach(f -> f.completeExceptionally(ex)); 184 | backoff.sleep(); 185 | return; 186 | } 187 | 188 | backoff.reset(); 189 | 190 | // Complete each future 191 | for (int i = 0; i < futures.size(); i++) { 192 | final CompletableFuture future = futures.get(i); 193 | future.complete(null); 194 | } 195 | }) 196 | 197 | // When batch is complete, process pending acks. 198 | .whenComplete((v, t) -> send()); 199 | 200 | return batch.size(); 201 | } 202 | 203 | @Override 204 | public void close() throws IOException { 205 | // TODO (dano): fail outstanding futures 206 | scheduler.shutdownNow(); 207 | try { 208 | scheduler.awaitTermination(30, SECONDS); 209 | } catch (InterruptedException e) { 210 | Thread.currentThread().interrupt(); 211 | } 212 | pubsub.close(); 213 | } 214 | 215 | /** 216 | * An outgoing ack with the future that should be completed when the ack is complete. 217 | */ 218 | private static class QueuedAck { 219 | 220 | private final String ackId; 221 | private final CompletableFuture future; 222 | 223 | public QueuedAck(final String ackId, final CompletableFuture future) { 224 | this.ackId = ackId; 225 | this.future = future; 226 | } 227 | } 228 | 229 | 230 | /** 231 | * Create a builder that can be used to build an {@link Acker}. 232 | */ 233 | public static Builder builder() { 234 | return new Builder(); 235 | } 236 | 237 | /** 238 | * A builder that can be used to build an {@link Acker}. 239 | */ 240 | public static class Builder { 241 | 242 | private Pubsub pubsub; 243 | private String project; 244 | private String subscription; 245 | private int concurrency = 64; 246 | private int batchSize = 1000; 247 | private Integer queueSize; 248 | private long maxLatencyMs = 1000; 249 | private int maxBackoffMultiplier = 0; 250 | 251 | /** 252 | * Set the {@link Pubsub} client to use. The client will be closed when this {@link Acker} is closed. 253 | * 254 | *

Note: The client should be configured to at least allow as many connections as the concurrency level of this 255 | * {@link Acker}.

256 | */ 257 | public Builder pubsub(final Pubsub pubsub) { 258 | this.pubsub = pubsub; 259 | return this; 260 | } 261 | 262 | /** 263 | * Set the Google Cloud project to ack on from. 264 | */ 265 | public Builder project(final String project) { 266 | this.project = project; 267 | return this; 268 | } 269 | 270 | /** 271 | * The subscription to ack on from. 272 | */ 273 | public Builder subscription(final String subscription) { 274 | this.subscription = subscription; 275 | return this; 276 | } 277 | 278 | /** 279 | * Set the Google Cloud Pub/Sub request concurrency level. Default is {@code 64}. 280 | */ 281 | public Builder concurrency(final int concurrency) { 282 | this.concurrency = concurrency; 283 | return this; 284 | } 285 | 286 | /** 287 | * Set the Google Cloud Pub/Sub ack batch size. Default is {@code 1000}. 288 | */ 289 | public Builder batchSize(final int batchSize) { 290 | this.batchSize = batchSize; 291 | return this; 292 | } 293 | 294 | /** 295 | * Set the ack queue size. Default is {@code batchSize * concurrency * 10}. 296 | */ 297 | public Builder queueSize(final Integer queueSize) { 298 | this.queueSize = queueSize; 299 | return this; 300 | } 301 | 302 | /** 303 | * Set the maximum latency in millis before sending an incomplete Google Cloud Pub/Sub ack batch request. 304 | * Default is {@code 1000 ms}. 305 | */ 306 | public Builder maxLatencyMs(final long maxLatencyMs) { 307 | this.maxLatencyMs = maxLatencyMs; 308 | return this; 309 | } 310 | 311 | /** 312 | * Set the maximum backoff multiplier. Default is {@code 0} (no backoff). 313 | */ 314 | public Builder maxBackoffMultiplier(final int maxBackoffMultiplier) { 315 | this.maxBackoffMultiplier = maxBackoffMultiplier; 316 | return this; 317 | } 318 | 319 | /** 320 | * Build an {@link Acker}. 321 | */ 322 | public Acker build() { 323 | return new Acker(this); 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/AcknowledgeRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | 40 | import java.util.List; 41 | 42 | import io.norberg.automatter.AutoMatter; 43 | 44 | @AutoMatter 45 | interface AcknowledgeRequest { 46 | 47 | List ackIds(); 48 | 49 | static AcknowledgeRequestBuilder builder() { 50 | return new AcknowledgeRequestBuilder(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Backoff.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2017 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.api.client.util.Sleeper; 40 | import java.util.Random; 41 | import java.util.concurrent.atomic.AtomicInteger; 42 | 43 | /** 44 | * Thread safe backoff. Assumes threads that use the same backoff instance all share the 45 | * same rate limit, and throttles best effort until reset isn't called 46 | */ 47 | public class Backoff { 48 | 49 | // non-final for testing only 50 | private Sleeper sleeper = Sleeper.DEFAULT; 51 | // non-final for testing only 52 | private Random random = new Random(); 53 | 54 | private final long initialInterval; 55 | private final int maxBackoffMultiplier; 56 | 57 | private final AtomicInteger backoffMultiplier = new AtomicInteger(1); 58 | 59 | private Backoff(Builder builder) { 60 | this.initialInterval = builder.initialInterval; 61 | this.maxBackoffMultiplier = builder.maxBackoffMultiplier; 62 | } 63 | 64 | public void sleep() { 65 | // maxBackoffMultiplier of 0 means no backoff 66 | if (maxBackoffMultiplier == 0) { 67 | return; 68 | } 69 | 70 | try { 71 | int backoff = backoffMultiplier.get(); 72 | 73 | if (backoff < maxBackoffMultiplier) { 74 | // if some other thread updated the backoff already, we don't care 75 | backoffMultiplier.compareAndSet(backoff, backoff + 1); 76 | } 77 | sleeper.sleep((long) (initialInterval * backoff * getRandomizationFactor())); 78 | } catch (InterruptedException e) { 79 | Thread.currentThread().interrupt(); 80 | } 81 | } 82 | 83 | public void reset() { 84 | backoffMultiplier.set(1); 85 | } 86 | 87 | /** 88 | * Return a random factor between 0.9 and 1.1 89 | */ 90 | private double getRandomizationFactor() { 91 | return 0.9 + random.nextDouble() * 0.2; 92 | } 93 | 94 | /** 95 | * Create a builder that can be used to build an {@link Acker}. 96 | */ 97 | public static Builder builder() { 98 | return new Builder(); 99 | } 100 | 101 | public static class Builder { 102 | 103 | private long initialInterval = 0; 104 | private int maxBackoffMultiplier = 0; 105 | 106 | /** 107 | * The initial interval in milliseconds between calls 108 | */ 109 | public Builder initialInterval(final long initialInterval) { 110 | this.initialInterval = initialInterval; 111 | return this; 112 | } 113 | 114 | /** 115 | * The maximum interval multiplier for backoff (0 is no backoff) 116 | */ 117 | public Builder maxBackoffMultiplier(final int maxBackoffMultiplier) { 118 | this.maxBackoffMultiplier = maxBackoffMultiplier; 119 | return this; 120 | } 121 | 122 | public Backoff build() { 123 | return new Backoff(this); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/ConfigurableSSLSocketFactory.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2016 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.io.IOException; 40 | import java.net.InetAddress; 41 | import java.net.Socket; 42 | 43 | import javax.net.ssl.SSLSocket; 44 | import javax.net.ssl.SSLSocketFactory; 45 | 46 | class ConfigurableSSLSocketFactory extends SSLSocketFactory { 47 | 48 | private final String[] enabledCipherSuites; 49 | private final SSLSocketFactory sslSocketFactory; 50 | 51 | ConfigurableSSLSocketFactory(final String[] enabledCipherSuites, final SSLSocketFactory sslSocketFactory) { 52 | this.enabledCipherSuites = enabledCipherSuites; 53 | this.sslSocketFactory = sslSocketFactory; 54 | } 55 | 56 | @Override 57 | public String[] getDefaultCipherSuites() { 58 | return enabledCipherSuites; 59 | } 60 | 61 | @Override 62 | public String[] getSupportedCipherSuites() { 63 | return enabledCipherSuites; 64 | } 65 | 66 | @Override 67 | public Socket createSocket(final Socket sock, final String host, final int port, final boolean autoClose) 68 | throws IOException { 69 | final SSLSocket s = (SSLSocket) sslSocketFactory.createSocket(sock, host, port, autoClose); 70 | if (enabledCipherSuites != null) { 71 | s.setEnabledCipherSuites(enabledCipherSuites); 72 | } 73 | return s; 74 | } 75 | 76 | @Override 77 | public Socket createSocket(final String host, final int port) throws IOException { 78 | final SSLSocket s = (SSLSocket) sslSocketFactory.createSocket(host, port); 79 | if (enabledCipherSuites != null) { 80 | s.setEnabledCipherSuites(enabledCipherSuites); 81 | } 82 | return s; 83 | } 84 | 85 | @Override 86 | public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) 87 | throws IOException { 88 | final SSLSocket s = (SSLSocket) sslSocketFactory.createSocket(host, port, localAddress, localPort); 89 | if (enabledCipherSuites != null) { 90 | s.setEnabledCipherSuites(enabledCipherSuites); 91 | } 92 | return s; 93 | } 94 | 95 | @Override 96 | public Socket createSocket(final InetAddress host, final int port) throws IOException { 97 | final SSLSocket s = (SSLSocket) sslSocketFactory.createSocket(host, port); 98 | if (enabledCipherSuites != null) { 99 | s.setEnabledCipherSuites(enabledCipherSuites); 100 | } 101 | return s; 102 | } 103 | 104 | @Override 105 | public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, 106 | final int localPort) 107 | throws IOException { 108 | final SSLSocket s = (SSLSocket) sslSocketFactory.createSocket(host, port, localAddress, localPort); 109 | if (enabledCipherSuites != null) { 110 | s.setEnabledCipherSuites(enabledCipherSuites); 111 | } 112 | return s; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Json.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.base.Throwables; 40 | 41 | import com.fasterxml.jackson.core.JsonProcessingException; 42 | import com.fasterxml.jackson.databind.ObjectMapper; 43 | import com.fasterxml.jackson.datatype.guava.GuavaModule; 44 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 45 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 46 | 47 | import java.io.IOException; 48 | import java.io.InputStream; 49 | import java.io.OutputStream; 50 | 51 | import io.norberg.automatter.jackson.AutoMatterModule; 52 | 53 | import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; 54 | import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; 55 | 56 | class Json { 57 | 58 | private static final ObjectMapper MAPPER = new ObjectMapper() 59 | .setSerializationInclusion(NON_EMPTY) 60 | .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) 61 | .registerModule(new AutoMatterModule()) 62 | .registerModule(new Jdk8Module()) 63 | .registerModule(new JavaTimeModule()) 64 | .registerModule(new GuavaModule()); 65 | 66 | static T read(final byte[] src, final Class cls) throws IOException { 67 | return MAPPER.readValue(src, cls); 68 | } 69 | 70 | static T read(final InputStream src, final Class cls) throws IOException { 71 | return MAPPER.readValue(src, cls); 72 | } 73 | 74 | static byte[] write(final Object value) { 75 | try { 76 | return MAPPER.writeValueAsBytes(value); 77 | } catch (JsonProcessingException e) { 78 | throw Throwables.propagate(e); 79 | } 80 | } 81 | 82 | static void write(final OutputStream stream, final Object value) throws IOException { 83 | try { 84 | MAPPER.writeValue(stream, value); 85 | } catch (JsonProcessingException e) { 86 | throw Throwables.propagate(e); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Message.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.base.CharMatcher; 40 | import com.google.common.io.BaseEncoding; 41 | 42 | import java.nio.ByteBuffer; 43 | import java.nio.CharBuffer; 44 | import java.time.Instant; 45 | import java.util.Base64; 46 | import java.util.Map; 47 | import java.util.Optional; 48 | 49 | import io.norberg.automatter.AutoMatter; 50 | 51 | import static java.nio.charset.StandardCharsets.UTF_8; 52 | 53 | @AutoMatter 54 | public interface Message { 55 | 56 | CharMatcher BASE64_MATCHER = CharMatcher.anyOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="); 57 | 58 | String data(); 59 | 60 | Map attributes(); 61 | 62 | Optional messageId(); 63 | 64 | Optional publishTime(); 65 | 66 | static MessageBuilder builder() { 67 | return new MessageBuilder(); 68 | } 69 | 70 | static Message of(final String data) { 71 | return builder().data(data).build(); 72 | } 73 | 74 | static Message ofEncoded(final CharSequence data) { 75 | return of(encode(data)); 76 | } 77 | 78 | static String encode(final CharSequence data) { 79 | return encode(CharBuffer.wrap(data)); 80 | } 81 | 82 | static String encode(final CharSequence data, final int start, final int end) { 83 | return encode(CharBuffer.wrap(data, start, end)); 84 | } 85 | 86 | static String encode(final CharBuffer data) { 87 | return encode(UTF_8.encode(data)); 88 | } 89 | 90 | static String encode(final char[] data) { 91 | return encode(UTF_8.encode(CharBuffer.wrap(data))); 92 | } 93 | 94 | static String encode(final ByteBuffer data) { 95 | if (data.hasArray()) { 96 | return encode(data.array(), data.arrayOffset(), data.arrayOffset() + data.remaining()); 97 | } 98 | final byte[] bytes = new byte[data.remaining()]; 99 | final int mark = data.position(); 100 | data.get(bytes); 101 | data.position(mark); 102 | return encode(bytes); 103 | } 104 | 105 | static String encode(final byte[] data, final int offset, final int length) { 106 | if (offset == 0 && data.length == length) { 107 | return encode(data); 108 | } 109 | return BaseEncoding.base64().encode(data, offset, length); 110 | } 111 | 112 | static String encode(final byte[] data) { 113 | return Base64.getEncoder().encodeToString(data); 114 | } 115 | 116 | default byte[] decodedData() { 117 | return Base64.getDecoder().decode(data()); 118 | } 119 | 120 | default CharSequence decodedDataUTF8() { 121 | return UTF_8.decode(ByteBuffer.wrap(decodedData())); 122 | } 123 | 124 | static boolean isEncoded(Message message) { 125 | return BASE64_MATCHER.matchesAllOf(message.data()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/ModifyAckDeadlineRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | 40 | import java.util.List; 41 | 42 | import io.norberg.automatter.AutoMatter; 43 | 44 | @AutoMatter 45 | interface ModifyAckDeadlineRequest { 46 | 47 | List ackIds(); 48 | 49 | int ackDeadlineSeconds(); 50 | 51 | static ModifyAckDeadlineRequestBuilder builder() { 52 | return new ModifyAckDeadlineRequestBuilder(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PublishRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | 40 | import java.util.List; 41 | 42 | import io.norberg.automatter.AutoMatter; 43 | 44 | import static java.util.Arrays.asList; 45 | 46 | @AutoMatter 47 | interface PublishRequest { 48 | 49 | List messages(); 50 | 51 | static PublishRequestBuilder builder() { 52 | return new PublishRequestBuilder(); 53 | } 54 | 55 | static PublishRequest of(List messages) { 56 | return builder().messages(messages).build(); 57 | } 58 | 59 | static PublishRequest of(Message... messages) { 60 | return of(asList(messages)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PublishResponse.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.List; 40 | 41 | import io.norberg.automatter.AutoMatter; 42 | 43 | import static java.util.Arrays.asList; 44 | 45 | @AutoMatter 46 | interface PublishResponse { 47 | 48 | List messageIds(); 49 | 50 | static PublishResponseBuilder builder() { 51 | return new PublishResponseBuilder(); 52 | } 53 | 54 | static PublishResponse of(List messageIds) { 55 | return builder().messageIds(messageIds).build(); 56 | } 57 | 58 | static PublishResponse of(String... messageIds) { 59 | return of(asList(messageIds)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PubsubException.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | public class PubsubException extends Exception { 40 | 41 | public PubsubException() { 42 | } 43 | 44 | public PubsubException(final String message) { 45 | super(message); 46 | } 47 | 48 | public PubsubException(final String message, final Throwable cause) { 49 | super(message, cause); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PubsubFuture.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.concurrent.CompletableFuture; 40 | import java.util.concurrent.CompletionStage; 41 | import java.util.concurrent.Executor; 42 | import java.util.function.BiConsumer; 43 | import java.util.function.BiFunction; 44 | import java.util.function.Consumer; 45 | import java.util.function.Function; 46 | 47 | public class PubsubFuture extends CompletableFuture { 48 | 49 | private final RequestInfo requestInfo; 50 | 51 | PubsubFuture(final RequestInfo requestInfo) { 52 | this.requestInfo = requestInfo; 53 | } 54 | 55 | public String operation() { 56 | return requestInfo.operation(); 57 | } 58 | 59 | public String method() { 60 | return requestInfo.method(); 61 | } 62 | 63 | public String uri() { 64 | return requestInfo.uri(); 65 | } 66 | 67 | public long payloadSize() { 68 | return requestInfo.payloadSize(); 69 | } 70 | 71 | @Override 72 | public PubsubFuture thenApply(final Function fn) { 73 | return wrap(super.thenApply(fn)); 74 | } 75 | 76 | @Override 77 | public PubsubFuture thenApplyAsync(final Function fn) { 78 | return wrap(super.thenApplyAsync(fn)); 79 | } 80 | 81 | @Override 82 | public PubsubFuture thenApplyAsync(final Function fn, final Executor executor) { 83 | return wrap(super.thenApplyAsync(fn, executor)); 84 | } 85 | 86 | @Override 87 | public PubsubFuture thenAccept(final Consumer action) { 88 | return wrap(super.thenAccept(action)); 89 | } 90 | 91 | @Override 92 | public PubsubFuture thenAcceptAsync(final Consumer action) { 93 | return wrap(super.thenAcceptAsync(action)); 94 | } 95 | 96 | @Override 97 | public PubsubFuture thenAcceptAsync(final Consumer action, final Executor executor) { 98 | return wrap(super.thenAcceptAsync(action, executor)); 99 | } 100 | 101 | @Override 102 | public PubsubFuture thenRun(final Runnable action) { 103 | return wrap(super.thenRun(action)); 104 | } 105 | 106 | @Override 107 | public PubsubFuture thenRunAsync(final Runnable action) { 108 | return wrap(super.thenRunAsync(action)); 109 | } 110 | 111 | @Override 112 | public PubsubFuture thenRunAsync(final Runnable action, final Executor executor) { 113 | return wrap(super.thenRunAsync(action, executor)); 114 | } 115 | 116 | @Override 117 | public PubsubFuture thenCombine(final CompletionStage other, 118 | final BiFunction fn) { 119 | return wrap(super.thenCombine(other, fn)); 120 | } 121 | 122 | @Override 123 | public PubsubFuture thenCombineAsync(final CompletionStage other, 124 | final BiFunction fn) { 125 | return wrap(super.thenCombineAsync(other, fn)); 126 | } 127 | 128 | @Override 129 | public PubsubFuture thenCombineAsync(final CompletionStage other, 130 | final BiFunction fn, 131 | final Executor executor) { 132 | return wrap(super.thenCombineAsync(other, fn, executor)); 133 | } 134 | 135 | @Override 136 | public PubsubFuture thenAcceptBoth(final CompletionStage other, 137 | final BiConsumer action) { 138 | return wrap(super.thenAcceptBoth(other, action)); 139 | } 140 | 141 | @Override 142 | public PubsubFuture thenAcceptBothAsync(final CompletionStage other, 143 | final BiConsumer action) { 144 | return wrap(super.thenAcceptBothAsync(other, action)); 145 | } 146 | 147 | @Override 148 | public PubsubFuture thenAcceptBothAsync(final CompletionStage other, 149 | final BiConsumer action, 150 | final Executor executor) { 151 | return wrap(super.thenAcceptBothAsync(other, action, executor)); 152 | } 153 | 154 | @Override 155 | public PubsubFuture runAfterBoth(final CompletionStage other, final Runnable action) { 156 | return wrap(super.runAfterBoth(other, action)); 157 | } 158 | 159 | @Override 160 | public PubsubFuture runAfterBothAsync(final CompletionStage other, final Runnable action) { 161 | return wrap(super.runAfterBothAsync(other, action)); 162 | } 163 | 164 | @Override 165 | public PubsubFuture runAfterBothAsync(final CompletionStage other, final Runnable action, 166 | final Executor executor) { 167 | return wrap(super.runAfterBothAsync(other, action, executor)); 168 | } 169 | 170 | @Override 171 | public PubsubFuture applyToEither(final CompletionStage other, 172 | final Function fn) { 173 | return wrap(super.applyToEither(other, fn)); 174 | } 175 | 176 | @Override 177 | public PubsubFuture applyToEitherAsync(final CompletionStage other, 178 | final Function fn) { 179 | return wrap(super.applyToEitherAsync(other, fn)); 180 | } 181 | 182 | @Override 183 | public PubsubFuture applyToEitherAsync(final CompletionStage other, 184 | final Function fn, 185 | final Executor executor) { 186 | return wrap(super.applyToEitherAsync(other, fn, executor)); 187 | } 188 | 189 | @Override 190 | public PubsubFuture acceptEither(final CompletionStage other, 191 | final Consumer action) { 192 | return wrap(super.acceptEither(other, action)); 193 | } 194 | 195 | @Override 196 | public PubsubFuture acceptEitherAsync(final CompletionStage other, 197 | final Consumer action) { 198 | return wrap(super.acceptEitherAsync(other, action)); 199 | } 200 | 201 | @Override 202 | public PubsubFuture acceptEitherAsync(final CompletionStage other, 203 | final Consumer action, 204 | final Executor executor) { 205 | return wrap(super.acceptEitherAsync(other, action, executor)); 206 | } 207 | 208 | @Override 209 | public PubsubFuture runAfterEither(final CompletionStage other, final Runnable action) { 210 | return wrap(super.runAfterEither(other, action)); 211 | } 212 | 213 | @Override 214 | public PubsubFuture runAfterEitherAsync(final CompletionStage other, final Runnable action) { 215 | return wrap(super.runAfterEitherAsync(other, action)); 216 | } 217 | 218 | @Override 219 | public PubsubFuture runAfterEitherAsync(final CompletionStage other, final Runnable action, 220 | final Executor executor) { 221 | return wrap(super.runAfterEitherAsync(other, action, executor)); 222 | } 223 | 224 | @Override 225 | public PubsubFuture thenCompose(final Function> fn) { 226 | return wrap(super.thenCompose(fn)); 227 | } 228 | 229 | @Override 230 | public PubsubFuture thenComposeAsync(final Function> fn) { 231 | return wrap(super.thenComposeAsync(fn)); 232 | } 233 | 234 | @Override 235 | public PubsubFuture thenComposeAsync(final Function> fn, 236 | final Executor executor) { 237 | return wrap(super.thenComposeAsync(fn, executor)); 238 | } 239 | 240 | @Override 241 | public PubsubFuture whenComplete(final BiConsumer action) { 242 | return wrap(super.whenComplete(action)); 243 | } 244 | 245 | @Override 246 | public PubsubFuture whenCompleteAsync(final BiConsumer action) { 247 | return wrap(super.whenCompleteAsync(action)); 248 | } 249 | 250 | @Override 251 | public PubsubFuture whenCompleteAsync(final BiConsumer action, 252 | final Executor executor) { 253 | return wrap(super.whenCompleteAsync(action, executor)); 254 | } 255 | 256 | @Override 257 | public PubsubFuture handle(final BiFunction fn) { 258 | return wrap(super.handle(fn)); 259 | } 260 | 261 | @Override 262 | public PubsubFuture handleAsync(final BiFunction fn) { 263 | return wrap(super.handleAsync(fn)); 264 | } 265 | 266 | @Override 267 | public PubsubFuture handleAsync(final BiFunction fn, 268 | final Executor executor) { 269 | return wrap(super.handleAsync(fn, executor)); 270 | } 271 | 272 | @Override 273 | public PubsubFuture toCompletableFuture() { 274 | return this; 275 | } 276 | 277 | @Override 278 | public PubsubFuture exceptionally(final Function fn) { 279 | return wrap(super.exceptionally(fn)); 280 | } 281 | 282 | @Override 283 | public boolean complete(final T value) { 284 | throw new UnsupportedOperationException(); 285 | } 286 | 287 | @Override 288 | public boolean completeExceptionally(final Throwable ex) { 289 | throw new UnsupportedOperationException(); 290 | } 291 | 292 | private PubsubFuture wrap(final CompletableFuture future) { 293 | final PubsubFuture pubsubFuture = new PubsubFuture<>(requestInfo); 294 | future.whenComplete((v, t) -> { 295 | if (t != null) { 296 | pubsubFuture.fail(t); 297 | } else { 298 | pubsubFuture.succeed(v); 299 | } 300 | }); 301 | return pubsubFuture; 302 | } 303 | 304 | public boolean succeed(final T value) { 305 | return super.complete(value); 306 | } 307 | 308 | public boolean fail(final Throwable ex) { 309 | return super.completeExceptionally(ex); 310 | } 311 | 312 | public static PubsubFuture of(final RequestInfo requestInfo) { 313 | return new PubsubFuture<>(requestInfo); 314 | } 315 | 316 | public static PubsubFuture succeededFuture(final RequestInfo requestInfo, final T value) { 317 | final PubsubFuture future = new PubsubFuture<>(requestInfo); 318 | future.succeed(value); 319 | return future; 320 | } 321 | 322 | public static PubsubFuture failedFuture(final RequestInfo requestInfo, final Throwable t) { 323 | final PubsubFuture future = new PubsubFuture<>(requestInfo); 324 | future.fail(t); 325 | return future; 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PullRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | 40 | import io.norberg.automatter.AutoMatter; 41 | 42 | @AutoMatter 43 | interface PullRequest { 44 | 45 | boolean returnImmediately(); 46 | 47 | int maxMessages(); 48 | 49 | static PullRequestBuilder builder() { 50 | return new PullRequestBuilder(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PullResponse.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.List; 40 | 41 | import io.norberg.automatter.AutoMatter; 42 | 43 | import static java.util.Arrays.asList; 44 | 45 | @AutoMatter 46 | interface PullResponse { 47 | 48 | List receivedMessages(); 49 | 50 | static PullResponseBuilder builder() { 51 | return new PullResponseBuilder(); 52 | } 53 | 54 | static PullResponse of(List messages) { 55 | return builder().receivedMessages(messages).build(); 56 | } 57 | 58 | static PullResponse of(ReceivedMessage... messages) { 59 | return of(asList(messages)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/PushConfig.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.Optional; 40 | 41 | import io.norberg.automatter.AutoMatter; 42 | 43 | @AutoMatter 44 | public interface PushConfig { 45 | 46 | Optional pushEndpoint(); 47 | 48 | static PushConfigBuilder builder() { 49 | return new PushConfigBuilder(); 50 | } 51 | 52 | static PushConfig of(String pushEndpoint) { 53 | return builder().pushEndpoint(pushEndpoint).build(); 54 | } 55 | 56 | static PushConfig of() { 57 | return builder().build(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/QueueFullException.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | public class QueueFullException extends PubsubException { 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/ReceivedMessage.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import io.norberg.automatter.AutoMatter; 40 | 41 | @AutoMatter 42 | public interface ReceivedMessage { 43 | 44 | String ackId(); 45 | 46 | Message message(); 47 | 48 | static ReceivedMessage of(final String ackId, final Message message) { 49 | return builder().ackId(ackId).message(message).build(); 50 | } 51 | 52 | static ReceivedMessage of(final String ackId, final String data) { 53 | return builder().ackId(ackId).message(Message.of(data)).build(); 54 | } 55 | 56 | static ReceivedMessage ofEncoded(final String ackId, final String data) { 57 | return builder().ackId(ackId).message(Message.ofEncoded(data)).build(); 58 | } 59 | 60 | static ReceivedMessageBuilder builder() { 61 | return new ReceivedMessageBuilder(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/RequestFailedException.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | public class RequestFailedException extends PubsubException { 40 | 41 | private final int statusCode; 42 | private final String statusMessage; 43 | 44 | public RequestFailedException(final int statusCode, final String statusMessage) { 45 | super(statusCode + " " + statusMessage); 46 | this.statusCode = statusCode; 47 | this.statusMessage = statusMessage; 48 | } 49 | 50 | public int statusCode() { 51 | return statusCode; 52 | } 53 | 54 | public String statusMessage() { 55 | return statusMessage; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/RequestInfo.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2016 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import io.norberg.automatter.AutoMatter; 40 | 41 | @AutoMatter 42 | public interface RequestInfo { 43 | 44 | String operation(); 45 | String method(); 46 | String uri(); 47 | long payloadSize(); 48 | 49 | static RequestInfoBuilder builder() { 50 | return new RequestInfoBuilder(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Subscription.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import static com.google.common.base.Preconditions.checkArgument; 40 | import static com.google.common.base.Strings.isNullOrEmpty; 41 | import static com.spotify.google.cloud.pubsub.client.Topic.canonicalTopic; 42 | import static com.spotify.google.cloud.pubsub.client.Topic.validateCanonicalTopic; 43 | 44 | import io.norberg.automatter.AutoMatter; 45 | import java.util.Optional; 46 | import java.util.regex.Pattern; 47 | 48 | @AutoMatter 49 | public interface Subscription { 50 | 51 | Pattern PATTERN = Pattern.compile("^projects/[^/]*/subscriptions/[^/]*$"); 52 | 53 | String PROJECTS = "projects"; 54 | String SUBSCRIPTIONS = "subscriptions"; 55 | 56 | String name(); 57 | 58 | String topic(); 59 | 60 | Optional pushConfig(); 61 | 62 | Optional ackDeadlineSeconds(); 63 | 64 | static SubscriptionBuilder builder() { 65 | return new SubscriptionBuilder(); 66 | } 67 | 68 | static Subscription of(String project, String name, String topic) { 69 | return of(canonicalSubscription(project, name), canonicalTopic(project, topic)); 70 | } 71 | 72 | static Subscription of(String canonicalSubscription, String canonicalTopic) { 73 | validateCanonicalSubscription(canonicalSubscription); 74 | validateCanonicalTopic(canonicalTopic); 75 | return builder().name(canonicalSubscription).topic(canonicalTopic).build(); 76 | } 77 | 78 | static String canonicalSubscription(final String project, final String subscription) { 79 | checkArgument(!isNullOrEmpty(project) && !project.contains("/"), "illegal project: %s", project); 80 | checkArgument(!isNullOrEmpty(subscription) && !subscription.contains("/") && !subscription.startsWith("goog"), 81 | "illegal subscription: %s", subscription); 82 | return PROJECTS + '/' + project + '/' + SUBSCRIPTIONS + '/' + subscription; 83 | } 84 | 85 | static void validateCanonicalSubscription(final String canonicalSubscription) { 86 | checkArgument(PATTERN.matcher(canonicalSubscription).matches(), "malformed subscription: %s", 87 | canonicalSubscription); 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/SubscriptionCreateRequest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.google.cloud.pubsub.client; 22 | 23 | import io.norberg.automatter.AutoMatter; 24 | import java.util.Optional; 25 | 26 | @AutoMatter 27 | interface SubscriptionCreateRequest { 28 | 29 | String topic(); 30 | 31 | Optional pushConfig(); 32 | 33 | Optional ackDeadlineSeconds(); 34 | 35 | static SubscriptionCreateRequestBuilder builder() { 36 | return new SubscriptionCreateRequestBuilder(); 37 | } 38 | 39 | static SubscriptionCreateRequest of(Subscription subscription) { 40 | return builder() 41 | .topic(subscription.topic()) 42 | .pushConfig(subscription.pushConfig()) 43 | .ackDeadlineSeconds(subscription.ackDeadlineSeconds()) 44 | .build(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/SubscriptionList.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.List; 40 | import java.util.Optional; 41 | 42 | import io.norberg.automatter.AutoMatter; 43 | 44 | import static java.util.Arrays.asList; 45 | 46 | @AutoMatter 47 | public interface SubscriptionList { 48 | 49 | List subscriptions(); 50 | 51 | Optional nextPageToken(); 52 | 53 | static SubscriptionListBuilder builder() { 54 | return new SubscriptionListBuilder(); 55 | } 56 | 57 | static SubscriptionList of(Iterable subscriptions) { 58 | return builder().subscriptions(subscriptions).build(); 59 | } 60 | 61 | static SubscriptionList of(Subscription... subscriptions) { 62 | return of(asList(subscriptions)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/Topic.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.regex.Pattern; 40 | 41 | import io.norberg.automatter.AutoMatter; 42 | 43 | import static com.google.common.base.Preconditions.checkArgument; 44 | import static com.google.common.base.Strings.isNullOrEmpty; 45 | 46 | @AutoMatter 47 | public interface Topic { 48 | 49 | Pattern PATTERN = Pattern.compile("^projects/[^/]*/topics/[^/]*$"); 50 | 51 | String PROJECTS = "projects"; 52 | String TOPICS = "topics"; 53 | 54 | String name(); 55 | 56 | static TopicBuilder builder() { 57 | return new TopicBuilder(); 58 | } 59 | 60 | static Topic of(String project, String topic) { 61 | return of(canonicalTopic(project, topic)); 62 | } 63 | 64 | static Topic of(String canonicalTopic) { 65 | return builder().name(canonicalTopic).build(); 66 | } 67 | 68 | static String canonicalTopic(final String project, final String topic) { 69 | checkArgument(!isNullOrEmpty(project) && !project.contains("/"), "illegal project: %s", project); 70 | checkArgument(!isNullOrEmpty(topic) && !topic.contains("/"), "illegal topic: %s", topic); 71 | return PROJECTS + '/' + project + '/' + TOPICS + '/' + topic; 72 | } 73 | 74 | static void validateCanonicalTopic(final String canonicalTopic) { 75 | checkArgument(PATTERN.matcher(canonicalTopic).matches(), "malformed topic: %s", canonicalTopic); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/google/cloud/pubsub/client/TopicList.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import java.util.List; 40 | import java.util.Optional; 41 | 42 | import io.norberg.automatter.AutoMatter; 43 | 44 | import static java.util.Arrays.asList; 45 | 46 | @AutoMatter 47 | public interface TopicList { 48 | 49 | List topics(); 50 | 51 | Optional nextPageToken(); 52 | 53 | static TopicListBuilder builder() { 54 | return new TopicListBuilder(); 55 | } 56 | 57 | static TopicList of(Iterable topics) { 58 | return builder().topics(topics).build(); 59 | } 60 | 61 | static TopicList of(Topic... topics) { 62 | return of(asList(topics)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/AckerTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2016 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import static com.google.common.collect.Iterables.concat; 40 | import static java.util.concurrent.TimeUnit.DAYS; 41 | import static java.util.stream.Collectors.toList; 42 | import static java.util.stream.IntStream.range; 43 | import static org.hamcrest.Matchers.contains; 44 | import static org.hamcrest.Matchers.is; 45 | import static org.hamcrest.Matchers.notNullValue; 46 | import static org.junit.Assert.assertThat; 47 | import static org.mockito.ArgumentMatchers.any; 48 | import static org.mockito.ArgumentMatchers.anyList; 49 | import static org.mockito.ArgumentMatchers.anyString; 50 | import static org.mockito.Mockito.never; 51 | import static org.mockito.Mockito.reset; 52 | import static org.mockito.Mockito.timeout; 53 | import static org.mockito.Mockito.verify; 54 | import static org.mockito.Mockito.when; 55 | 56 | import com.google.common.collect.ImmutableSet; 57 | import java.util.List; 58 | import java.util.Set; 59 | import java.util.concurrent.BlockingQueue; 60 | import java.util.concurrent.ExecutionException; 61 | import java.util.concurrent.LinkedBlockingQueue; 62 | import java.util.concurrent.TimeUnit; 63 | import org.junit.After; 64 | import org.junit.Before; 65 | import org.junit.Test; 66 | import org.junit.runner.RunWith; 67 | import org.mockito.ArgumentCaptor; 68 | import org.mockito.Captor; 69 | import org.mockito.Mock; 70 | import org.mockito.junit.MockitoJUnitRunner; 71 | 72 | @RunWith(MockitoJUnitRunner.class) 73 | public class AckerTest { 74 | 75 | private static final String BASE_URI = "https://mock-pubsub/v1/"; 76 | 77 | @Mock Pubsub pubsub; 78 | 79 | @Captor ArgumentCaptor>> batchFutureCaptor; 80 | 81 | final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); 82 | 83 | private Acker acker; 84 | 85 | @Before 86 | public void setUp() { 87 | setUpPubsubClient(); 88 | 89 | acker = Acker.builder() 90 | .project("test") 91 | .subscription("subscription") 92 | .pubsub(pubsub) 93 | .build(); 94 | } 95 | 96 | @After 97 | public void tearDown() throws Exception { 98 | acker.close(); 99 | } 100 | 101 | @Test 102 | public void testLatencyBoundedBatchingSingleMessage() throws InterruptedException, ExecutionException { 103 | // Ack a message 104 | acker.acknowledge("m1"); 105 | 106 | // Check that the acker eventually times out gathering id's for the batch and sends the single id. 107 | final Request request = requestQueue.take(); 108 | assertThat(request.ids.size(), is(1)); 109 | } 110 | 111 | @Test 112 | public void testLatencyBoundedBatchingTwoMessages() throws InterruptedException, ExecutionException { 113 | // Ack two messages 114 | acker.acknowledge("m1"); 115 | acker.acknowledge("m2"); 116 | 117 | // Check that the acker eventually times out gathering id's for the batch and sends the two id's. 118 | final Request request = requestQueue.take(); 119 | assertThat(request.ids, contains("m1", "m2")); 120 | } 121 | 122 | @Test 123 | public void testSizeBoundedBatching() throws InterruptedException, ExecutionException { 124 | acker = Acker.builder() 125 | .project("test") 126 | .subscription("subscription") 127 | .pubsub(pubsub) 128 | .batchSize(2) 129 | .maxLatencyMs(DAYS.toMillis(1)) 130 | .build(); 131 | 132 | // Ack a single message 133 | acker.acknowledge("m1"); 134 | 135 | // Verify that the batch is not sent 136 | Thread.sleep(1000); 137 | verify(pubsub, never()).acknowledge(anyString(), anyString(), any(String[].class)); 138 | verify(pubsub, never()).acknowledge(anyString(), anyString(), anyList()); 139 | verify(pubsub, never()).acknowledge(anyString(), anyList()); 140 | 141 | // Ack one more message, completing the batch. 142 | acker.acknowledge("m2"); 143 | 144 | // Check that the batch got sent. 145 | verify(pubsub, timeout(5000)).acknowledge(anyString(), anyString(), anyList()); 146 | final Request request = requestQueue.take(); 147 | assertThat(request.ids.size(), is(2)); 148 | } 149 | 150 | @Test 151 | public void verifyConcurrentBacklogConsumption() throws Exception { 152 | 153 | acker = Acker.builder() 154 | .project("test") 155 | .subscription("subscription") 156 | .pubsub(pubsub) 157 | .concurrency(2) 158 | .batchSize(2) 159 | .queueSize(100) 160 | .build(); 161 | 162 | // Saturate concurrency with two id's 163 | acker.acknowledge("a0"); 164 | final Request ra0 = requestQueue.take(); 165 | 166 | acker.acknowledge("a1"); 167 | final Request ra1 = requestQueue.take(); 168 | 169 | // Enqueue enough for at least two more batches 170 | final List m1 = range(0, 4).mapToObj(i -> "b" + i).collect(toList()); 171 | m1.forEach(acker::acknowledge); 172 | 173 | // Complete the first two requests 174 | ra0.future.succeed(null); 175 | ra1.future.succeed(null); 176 | 177 | // Verify that two batches kicked off concurrently and that we got all four messages in the two batches 178 | final Request r1a = requestQueue.poll(30, TimeUnit.SECONDS); 179 | final Request r1b = requestQueue.poll(30, TimeUnit.SECONDS); 180 | assertThat(r1a, is(notNullValue())); 181 | assertThat(r1b, is(notNullValue())); 182 | final Set r1received = ImmutableSet.copyOf(concat(r1a.ids, r1b.ids)); 183 | assertThat(r1received, is(ImmutableSet.copyOf(m1))); 184 | } 185 | 186 | @Test 187 | public void verifyCloseWillClosePubsubClient() throws Exception { 188 | acker = Acker.builder() 189 | .project("test") 190 | .subscription("subscription") 191 | .pubsub(pubsub) 192 | .build(); 193 | acker.close(); 194 | verify(pubsub).close(); 195 | } 196 | 197 | private void setUpPubsubClient() { 198 | reset(pubsub); 199 | when(pubsub.acknowledge(anyString(), anyString(), anyList())) 200 | .thenAnswer(invocation -> { 201 | final String project = invocation.getArgument(0, String.class); 202 | final String subscription = invocation.getArgument(1, String.class); 203 | @SuppressWarnings("unchecked") final List ackIds = 204 | (List) invocation.getArgument(2, List.class); 205 | final String canonicalSubscription = Subscription.canonicalSubscription(project, subscription); 206 | final String uri = BASE_URI + canonicalSubscription + ":acknowledge"; 207 | final RequestInfo requestInfo = RequestInfo.builder() 208 | .operation("acknowledge") 209 | .method("POST") 210 | .uri(uri) 211 | .payloadSize(4711) 212 | .build(); 213 | final PubsubFuture future = new PubsubFuture<>(requestInfo); 214 | requestQueue.add(new Request(ackIds, future)); 215 | return future; 216 | }); 217 | } 218 | 219 | private static class Request { 220 | 221 | final List ids; 222 | final PubsubFuture future; 223 | 224 | Request(final List ids, 225 | final PubsubFuture future) { 226 | this.ids = ids; 227 | this.future = future; 228 | } 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/AssertWithTimeout.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.hamcrest.Matcher; 40 | 41 | import java.util.concurrent.TimeUnit; 42 | import java.util.function.Supplier; 43 | 44 | import static org.junit.Assert.assertThat; 45 | 46 | class AssertWithTimeout { 47 | 48 | static void assertThatWithin(final long timeout, final TimeUnit unit, 49 | final Supplier actual, final Matcher matcher) { 50 | final long start = System.nanoTime(); 51 | final long deadline = start + unit.toNanos(timeout); 52 | while (true) { 53 | if (System.nanoTime() > deadline) { 54 | assertThat(actual.get(), matcher); 55 | return; 56 | } 57 | if (matcher.matches(actual.get())) { 58 | return; 59 | } 60 | try { 61 | Thread.sleep(50); 62 | } catch (InterruptedException e) { 63 | Thread.currentThread().interrupt(); 64 | return; 65 | } 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/BackoffTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2017 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import static org.mockito.ArgumentMatchers.eq; 40 | import static org.mockito.Mockito.inOrder; 41 | import static org.mockito.Mockito.times; 42 | import static org.mockito.Mockito.verifyNoInteractions; 43 | import static org.mockito.Mockito.verifyNoMoreInteractions; 44 | import static org.mockito.Mockito.when; 45 | 46 | import com.google.api.client.util.Sleeper; 47 | import java.util.Random; 48 | import org.junit.Test; 49 | import org.junit.runner.RunWith; 50 | import org.mockito.InOrder; 51 | import org.mockito.InjectMocks; 52 | import org.mockito.Mock; 53 | import org.mockito.junit.MockitoJUnitRunner; 54 | 55 | @RunWith(MockitoJUnitRunner.class) 56 | public class BackoffTest { 57 | 58 | @Mock 59 | private Sleeper sleeper; 60 | @Mock 61 | private Random random; 62 | 63 | @InjectMocks 64 | public Backoff backoff = Backoff.builder().initialInterval(111).maxBackoffMultiplier(5).build(); 65 | @InjectMocks 66 | public Backoff noBackoff = Backoff.builder().initialInterval(111).maxBackoffMultiplier(0).build(); 67 | 68 | @Test 69 | public void testBackoff() throws Exception { 70 | when(random.nextDouble()).thenReturn(0.5); 71 | 72 | backoff.reset(); 73 | backoff.sleep(); 74 | backoff.sleep(); 75 | backoff.sleep(); 76 | backoff.sleep(); 77 | backoff.sleep(); // max is reached 78 | backoff.sleep(); 79 | backoff.reset(); // reset back 80 | backoff.sleep(); 81 | backoff.sleep(); 82 | 83 | InOrder inOrder = inOrder(sleeper); 84 | inOrder.verify(sleeper).sleep(eq(111L)); 85 | inOrder.verify(sleeper).sleep(eq(222L)); 86 | inOrder.verify(sleeper).sleep(eq(333L)); 87 | inOrder.verify(sleeper).sleep(eq(444L)); 88 | inOrder.verify(sleeper, times(2)).sleep(eq(555L)); 89 | inOrder.verify(sleeper).sleep(eq(111L)); 90 | inOrder.verify(sleeper).sleep(eq(222L)); 91 | 92 | verifyNoMoreInteractions(sleeper); 93 | } 94 | 95 | @Test 96 | public void testBackoffRandomizer() throws Exception { 97 | when(random.nextDouble()).thenReturn(0.5, 0.1, 0.9); 98 | 99 | backoff.reset(); 100 | backoff.sleep(); 101 | backoff.reset(); 102 | backoff.sleep(); 103 | backoff.reset(); 104 | backoff.sleep(); 105 | 106 | InOrder inOrder = inOrder(sleeper); 107 | inOrder.verify(sleeper).sleep(eq(111L)); 108 | inOrder.verify(sleeper).sleep(eq(102L)); 109 | inOrder.verify(sleeper).sleep(eq(119L)); 110 | 111 | verifyNoMoreInteractions(sleeper); 112 | } 113 | 114 | @Test 115 | public void testNoBackoff() throws Exception { 116 | noBackoff.reset(); 117 | noBackoff.sleep(); 118 | noBackoff.sleep(); 119 | verifyNoInteractions(sleeper); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/JsonTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.collect.ImmutableMap; 40 | 41 | import org.junit.Test; 42 | 43 | import java.io.ByteArrayOutputStream; 44 | import java.io.IOException; 45 | import java.util.Arrays; 46 | import java.util.Map; 47 | import java.util.Optional; 48 | 49 | import io.norberg.automatter.AutoMatter; 50 | 51 | import static org.hamcrest.Matchers.is; 52 | import static org.junit.Assert.assertThat; 53 | import static org.junit.Assert.assertTrue; 54 | 55 | public class JsonTest { 56 | 57 | @Test 58 | public void testReadGuava() throws Exception { 59 | final Map expected = ImmutableMap.of("foo", "bar"); 60 | final ImmutableMap value = Json.read("{\"foo\":\"bar\"}".getBytes("UTF-8"), ImmutableMap.class); 61 | assertThat(value, is(expected)); 62 | } 63 | 64 | @AutoMatter 65 | public interface ValueWithOptional { 66 | 67 | Optional foo(); 68 | } 69 | 70 | @Test 71 | public void testReadEmptyOptional() throws Exception { 72 | final ValueWithOptional expected = new ValueWithOptionalBuilder().build(); 73 | final ValueWithOptional value = Json.read("{}".getBytes("UTF-8"), ValueWithOptional.class); 74 | assertThat(value, is(expected)); 75 | } 76 | 77 | @Test 78 | public void testReadPresentOptional() throws Exception { 79 | final ValueWithOptional expected = new ValueWithOptionalBuilder().foo("bar").build(); 80 | final ValueWithOptional value = Json.read("{\"foo\":\"bar\"}".getBytes("UTF-8"), ValueWithOptional.class); 81 | assertThat(value, is(expected)); 82 | } 83 | 84 | @Test 85 | public void testWriteGuava() throws Exception { 86 | final byte[] expected = "{\"foo\":\"bar\"}".getBytes("UTF-8"); 87 | final ImmutableMap value = ImmutableMap.of("foo", "bar"); 88 | testWrite(expected, value); 89 | } 90 | 91 | @Test 92 | public void testWritePresentOptional() throws Exception { 93 | final byte[] expected = "{\"foo\":\"bar\"}".getBytes("UTF-8"); 94 | final ValueWithOptional value = new ValueWithOptionalBuilder().foo("bar").build(); 95 | testWrite(expected, value); 96 | } 97 | 98 | @Test 99 | public void testWriteEmptyOptional() throws Exception { 100 | final byte[] expected = "{}".getBytes("UTF-8"); 101 | final ValueWithOptional value = new ValueWithOptionalBuilder().build(); 102 | testWrite(expected, value); 103 | } 104 | 105 | private void testWrite(final byte[] expected, final Object value) throws IOException { 106 | final byte[] json = Json.write(value); 107 | assertTrue(Arrays.equals(json, expected)); 108 | 109 | final ByteArrayOutputStream stream = new ByteArrayOutputStream(); 110 | Json.write(stream, value); 111 | assertTrue(Arrays.equals(stream.toByteArray(), expected)); 112 | } 113 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/MessageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.io.BaseEncoding; 40 | 41 | import org.junit.Test; 42 | 43 | import static com.spotify.google.cloud.pubsub.client.Message.encode; 44 | import static com.spotify.google.cloud.pubsub.client.Message.isEncoded; 45 | import static org.hamcrest.Matchers.is; 46 | import static org.junit.Assert.assertThat; 47 | 48 | public class MessageTest { 49 | 50 | @Test 51 | public void testOf() throws Exception { 52 | final String encoded = BaseEncoding.base64().encode("hello world".getBytes("UTF-8")); 53 | final Message message = Message.of(encoded); 54 | assertThat(message.data(), is(encoded)); 55 | } 56 | 57 | @Test 58 | public void testOfEncoded() throws Exception { 59 | final String encoded = BaseEncoding.base64().encode("hello world".getBytes("UTF-8")); 60 | final Message message = Message.ofEncoded("hello world"); 61 | assertThat(message.data(), is(encoded)); 62 | } 63 | 64 | @Test 65 | public void testDecodedData() throws Exception { 66 | final byte[] data = {1, 17, -24, 127, 0, -1}; 67 | final Message message = Message.of(encode(data)); 68 | assertThat(message.decodedData(), is(data)); 69 | } 70 | 71 | @Test 72 | public void testDecodedDataUTF8() throws Exception { 73 | final Message message = Message.ofEncoded("hello world"); 74 | assertThat(message.decodedDataUTF8().toString(), is("hello world")); 75 | } 76 | 77 | @Test 78 | public void testIsEncoded() throws Exception { 79 | assertThat(isEncoded(Message.of("Zg==")), is(true)); 80 | assertThat(isEncoded(Message.of("Zm8=")), is(true)); 81 | assertThat(isEncoded(Message.of("Zm8")), is(true)); 82 | 83 | assertThat(isEncoded(Message.of("räksmörgås")), is(false)); 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PublishRequestTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.junit.Test; 40 | 41 | import static org.hamcrest.Matchers.contains; 42 | import static org.junit.Assert.assertThat; 43 | 44 | public class PublishRequestTest { 45 | 46 | @Test 47 | public void testOf() throws Exception { 48 | final Message[] messages = {Message.ofEncoded("m1"), Message.ofEncoded("m2")}; 49 | final PublishRequest publishRequest = PublishRequest.of(messages); 50 | assertThat(publishRequest.messages(), contains(messages)); 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PublisherFailureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.junit.Before; 40 | import org.junit.Test; 41 | import org.junit.runner.RunWith; 42 | import org.mockito.Mock; 43 | 44 | import java.util.List; 45 | import java.util.concurrent.BlockingQueue; 46 | import java.util.concurrent.CompletableFuture; 47 | import java.util.concurrent.ConcurrentHashMap; 48 | import java.util.concurrent.ConcurrentMap; 49 | import java.util.concurrent.ExecutionException; 50 | import java.util.concurrent.LinkedBlockingQueue; 51 | import java.util.concurrent.TimeoutException; 52 | import org.mockito.junit.MockitoJUnitRunner; 53 | 54 | import static java.util.Collections.singletonList; 55 | import static org.hamcrest.Matchers.is; 56 | import static org.junit.Assert.assertThat; 57 | import static org.mockito.ArgumentMatchers.anyList; 58 | import static org.mockito.ArgumentMatchers.anyString; 59 | import static org.mockito.Mockito.when; 60 | 61 | @RunWith(MockitoJUnitRunner.class) 62 | public class PublisherFailureTest { 63 | 64 | @Mock Pubsub pubsub; 65 | 66 | Publisher publisher; 67 | 68 | final ConcurrentMap>>> topics = new ConcurrentHashMap<>(); 69 | 70 | @Before 71 | public void setUp() { 72 | publisher = Publisher.builder() 73 | .pubsub(pubsub) 74 | .project("test") 75 | .batchSize(1) 76 | .concurrency(1) 77 | .build(); 78 | 79 | final RequestInfo requestInfo = RequestInfo.builder() 80 | .operation("publish") 81 | .method("POST") 82 | .uri("/publish") 83 | .payloadSize(4711) 84 | .build(); 85 | 86 | when(pubsub.publish(anyString(), anyString(), anyList())) 87 | .thenAnswer(invocation -> { 88 | final String topic = (String) invocation.getArguments()[1]; 89 | final PubsubFuture> future = new PubsubFuture<>(requestInfo); 90 | final BlockingQueue>> queue = topics.get(topic); 91 | queue.add(future); 92 | return future; 93 | }); 94 | } 95 | 96 | @Test 97 | public void testTimeout() throws InterruptedException, ExecutionException { 98 | final LinkedBlockingQueue>> t1 = new LinkedBlockingQueue<>(); 99 | final LinkedBlockingQueue>> t2 = new LinkedBlockingQueue<>(); 100 | topics.put("t1", t1); 101 | topics.put("t2", t2); 102 | 103 | final Message m1 = Message.of("1"); 104 | final Message m2 = Message.of("2"); 105 | 106 | // Fail the first request 107 | final CompletableFuture f1 = publisher.publish("t1", m1); 108 | final PubsubFuture> r1 = t1.take(); 109 | r1.fail(new TimeoutException()); 110 | 111 | // Verify that the second request goes through 112 | final CompletableFuture f2 = publisher.publish("t2", m2); 113 | final PubsubFuture> r2 = t2.take(); 114 | r2.succeed(singletonList("id2")); 115 | final String id2 = f2.get(); 116 | assertThat(id2, is(r2.get().get(0))); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PubsubFutureTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | package com.spotify.google.cloud.pubsub.client; 22 | 23 | import static org.hamcrest.Matchers.instanceOf; 24 | import static org.hamcrest.Matchers.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | import java.util.concurrent.CompletionException; 28 | import java.util.concurrent.atomic.AtomicReference; 29 | import org.junit.Rule; 30 | import org.junit.Test; 31 | import org.junit.rules.ExpectedException; 32 | import org.junit.runner.RunWith; 33 | import org.mockito.Mock; 34 | import org.mockito.junit.MockitoJUnitRunner; 35 | 36 | @RunWith(MockitoJUnitRunner.class) 37 | public class PubsubFutureTest { 38 | 39 | @Rule public ExpectedException exception = ExpectedException.none(); 40 | 41 | @Mock RequestInfo requestInfo; 42 | 43 | @Test 44 | public void testToCompletableFutureErrorPropagation() throws Exception { 45 | final PubsubFuture f1 = new PubsubFuture<>(requestInfo); 46 | final PubsubFuture f2 = f1.toCompletableFuture(); 47 | 48 | final Exception cause = new Exception(); 49 | 50 | f1.fail(cause); 51 | 52 | assertThat(f2.isDone(), is(true)); 53 | assertThat(f2.isCompletedExceptionally(), is(true)); 54 | 55 | exception.expectCause(is(cause)); 56 | 57 | f2.get(); 58 | } 59 | 60 | @Test 61 | public void testToCompletableFutureResultPropagation() throws Exception { 62 | final PubsubFuture f1 = new PubsubFuture<>(requestInfo); 63 | final PubsubFuture f2 = f1.toCompletableFuture(); 64 | 65 | final String value = "foo"; 66 | 67 | f1.succeed(value); 68 | 69 | assertThat(f2.get(), is(value)); 70 | } 71 | 72 | @Test 73 | public void testCompletionExceptionPropagation() throws Exception { 74 | final PubsubFuture f1 = new PubsubFuture<>(requestInfo); 75 | final PubsubFuture f2 = f1.thenApply(r -> r); 76 | 77 | final AtomicReference whenCompleteThrowable = new AtomicReference<>(); 78 | f2.whenComplete((v, t) -> whenCompleteThrowable.set(t)); 79 | 80 | final Exception cause = new Exception(); 81 | f1.fail(cause); 82 | 83 | assertThat(f2.isDone(), is(true)); 84 | assertThat(f2.isCompletedExceptionally(), is(true)); 85 | 86 | assertThat(whenCompleteThrowable.get(), instanceOf(CompletionException.class)); 87 | assertThat(whenCompleteThrowable.get().getCause(), is(cause)); 88 | 89 | exception.expectCause(is(cause)); 90 | 91 | f2.get(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PullResponseTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.junit.Test; 40 | 41 | import static org.hamcrest.Matchers.contains; 42 | import static org.junit.Assert.assertThat; 43 | 44 | public class PullResponseTest { 45 | 46 | @Test 47 | public void testOf() throws Exception { 48 | final ReceivedMessage[] receivedMessages = {ReceivedMessage.ofEncoded("a1", "m1"), 49 | ReceivedMessage.ofEncoded("a2", "m2")}; 50 | final PullResponse pullResponse = PullResponse.of(receivedMessages); 51 | assertThat(pullResponse.receivedMessages(), contains(receivedMessages)); 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PullerTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2016 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import static java.util.Arrays.asList; 40 | import static java.util.concurrent.TimeUnit.SECONDS; 41 | import static org.awaitility.Awaitility.await; 42 | import static org.hamcrest.Matchers.is; 43 | import static org.hamcrest.Matchers.nullValue; 44 | import static org.junit.Assert.assertThat; 45 | import static org.mockito.ArgumentMatchers.any; 46 | import static org.mockito.ArgumentMatchers.anyBoolean; 47 | import static org.mockito.ArgumentMatchers.anyInt; 48 | import static org.mockito.ArgumentMatchers.anyString; 49 | import static org.mockito.Mockito.reset; 50 | import static org.mockito.Mockito.timeout; 51 | import static org.mockito.Mockito.verify; 52 | import static org.mockito.Mockito.when; 53 | 54 | import java.util.Collections; 55 | import java.util.List; 56 | import java.util.concurrent.BlockingQueue; 57 | import java.util.concurrent.CompletableFuture; 58 | import java.util.concurrent.LinkedBlockingQueue; 59 | import java.util.concurrent.Semaphore; 60 | import java.util.function.Supplier; 61 | import org.junit.After; 62 | import org.junit.Before; 63 | import org.junit.Test; 64 | import org.junit.runner.RunWith; 65 | import org.mockito.Mock; 66 | import org.mockito.junit.MockitoJUnitRunner; 67 | 68 | @RunWith(MockitoJUnitRunner.class) 69 | public class PullerTest { 70 | 71 | private static final String BASE_URI = "https://mock-pubsub/v1/"; 72 | private static final String SUBSCRIPTION = "test-subscription"; 73 | private static final String PROJECT = "test-project"; 74 | 75 | @Mock Pubsub pubsub; 76 | 77 | @Mock Puller.MessageHandler handler; 78 | 79 | final BlockingQueue requestQueue = new LinkedBlockingQueue<>(); 80 | 81 | private Puller puller; 82 | 83 | @Before 84 | public void setUp() { 85 | setUpPubsubClient(); 86 | } 87 | 88 | @After 89 | public void tearDown() throws Exception { 90 | puller.close(); 91 | } 92 | 93 | @Test 94 | public void testConfigurationGetters() throws Exception { 95 | puller = Puller.builder() 96 | .project(PROJECT) 97 | .subscription(SUBSCRIPTION) 98 | .pubsub(pubsub) 99 | .messageHandler(handler) 100 | .concurrency(3) 101 | .maxOutstandingMessages(4) 102 | .batchSize(5) 103 | .maxAckQueueSize(10) 104 | .pullIntervalMillis(1000) 105 | .build(); 106 | 107 | assertThat(puller.maxAckQueueSize(), is(10)); 108 | assertThat(puller.concurrency(), is(3)); 109 | assertThat(puller.maxOutstandingMessages(), is(4)); 110 | assertThat(puller.batchSize(), is(5)); 111 | assertThat(puller.subscription(), is(SUBSCRIPTION)); 112 | assertThat(puller.project(), is(PROJECT)); 113 | assertThat(puller.pullIntervalMillis(), is(1000L)); 114 | } 115 | 116 | @Test 117 | public void testPulling() throws Exception { 118 | puller = Puller.builder() 119 | .project(PROJECT) 120 | .subscription(SUBSCRIPTION) 121 | .pubsub(pubsub) 122 | .messageHandler(handler) 123 | .concurrency(2) 124 | .build(); 125 | 126 | // Immediately handle all messages 127 | when(handler.handleMessage(any(Puller.class), any(String.class), any(Message.class), anyString())) 128 | .thenAnswer(invocation -> { 129 | String ackId = invocation.getArgument(3, String.class); 130 | return CompletableFuture.completedFuture(ackId); 131 | }); 132 | 133 | final Request r1 = requestQueue.take(); 134 | final Request r2 = requestQueue.take(); 135 | 136 | assertThat(puller.outstandingRequests(), is(2)); 137 | 138 | // Verify that concurrency limit is not exceeded 139 | final Request unexpected = requestQueue.poll(1, SECONDS); 140 | assertThat(unexpected, is(nullValue())); 141 | 142 | // Complete first request 143 | final List b1 = asList(ReceivedMessage.of("i1", "m1"), 144 | ReceivedMessage.of("i2", "m2")); 145 | r1.future.succeed(b1); 146 | verify(handler, timeout(1000)).handleMessage(puller, SUBSCRIPTION, b1.get(0).message(), b1.get(0).ackId()); 147 | verify(handler, timeout(1000)).handleMessage(puller, SUBSCRIPTION, b1.get(1).message(), b1.get(1).ackId()); 148 | 149 | // Verify that another request is made 150 | final Request r3 = requestQueue.take(); 151 | 152 | assertThat(puller.outstandingRequests(), is(2)); 153 | 154 | // Complete second request 155 | final List b2 = asList(ReceivedMessage.of("i3", "m3"), 156 | ReceivedMessage.of("i4", "m4")); 157 | r2.future.succeed(b2); 158 | verify(handler, timeout(1000)).handleMessage(puller, SUBSCRIPTION, b2.get(0).message(), b2.get(0).ackId()); 159 | verify(handler, timeout(1000)).handleMessage(puller, SUBSCRIPTION, b2.get(1).message(), b2.get(1).ackId()); 160 | } 161 | 162 | @Test 163 | public void shouldDecrementOutstandingMessagesOnHandlerError() throws Exception { 164 | assertDecrementsOutstandingMessagesOnHandlerError(StackOverflowError::new); 165 | } 166 | 167 | @Test 168 | public void shouldDecrementOutstandingMessagesOnHandlerRuntimeException() throws Exception { 169 | assertDecrementsOutstandingMessagesOnHandlerError(RuntimeException::new); 170 | } 171 | 172 | private void assertDecrementsOutstandingMessagesOnHandlerError(Supplier t) throws InterruptedException { 173 | puller = Puller.builder() 174 | .project(PROJECT) 175 | .subscription(SUBSCRIPTION) 176 | .pubsub(pubsub) 177 | .messageHandler(handler) 178 | .concurrency(1) 179 | .build(); 180 | 181 | // Set up a handler that throws an Error 182 | final Semaphore semaphore = new Semaphore(0); 183 | when(handler.handleMessage(any(Puller.class), any(String.class), any(Message.class), anyString())) 184 | .thenAnswer(invocation -> { 185 | semaphore.acquire(); 186 | throw t.get(); 187 | }); 188 | 189 | // Return a single message 190 | final Request r = requestQueue.take(); 191 | final ReceivedMessage m = ReceivedMessage.of("i1", "m1"); 192 | CompletableFuture.supplyAsync(() -> r.future.succeed(Collections.singletonList(m))); 193 | 194 | // Wait for the handler to get called 195 | verify(handler, timeout(1000)).handleMessage(puller, SUBSCRIPTION, m.message(), m.ackId()); 196 | 197 | // Verify that the outstanding message count was incremented 198 | assertThat(puller.outstandingMessages(), is(1)); 199 | 200 | // Make handler throw 201 | semaphore.release(); 202 | 203 | // Verify that the outstanding messsage count was decremented 204 | await().atMost(30, SECONDS).until(() -> puller.outstandingMessages() == 0); 205 | } 206 | 207 | @Test 208 | public void testMaxOutstandingMessagesLimit() throws Exception { 209 | puller = Puller.builder() 210 | .project(PROJECT) 211 | .subscription(SUBSCRIPTION) 212 | .pubsub(pubsub) 213 | .messageHandler(handler) 214 | .maxOutstandingMessages(3) 215 | .concurrency(2) 216 | .build(); 217 | 218 | final BlockingQueue> futures = new LinkedBlockingQueue<>(); 219 | 220 | when(handler.handleMessage(any(Puller.class), any(String.class), any(Message.class), anyString())) 221 | .thenAnswer(invocation -> { 222 | final CompletableFuture f = new CompletableFuture<>(); 223 | futures.add(f); 224 | final String ackId = invocation.getArgument(3, String.class); 225 | return f.thenApply(ignore -> ackId); 226 | }); 227 | 228 | final Request r1 = requestQueue.take(); 229 | final Request r2 = requestQueue.take(); 230 | 231 | assertThat(puller.outstandingRequests(), is(2)); 232 | 233 | // Complete requests without immediately handling messages 234 | final List b1 = asList(ReceivedMessage.of("i1", "m1"), 235 | ReceivedMessage.of("i2", "m2"), 236 | ReceivedMessage.of("i3", "m3"), 237 | ReceivedMessage.of("i4", "m4")); 238 | r1.future.succeed(b1); 239 | 240 | // Verify that no new request are made until messages are handled 241 | assertThat(requestQueue.poll(1, SECONDS), is(nullValue())); 242 | assertThat(puller.outstandingRequests(), is(1)); 243 | assertThat(puller.outstandingMessages(), is(4)); 244 | 245 | // Complete handling of a single message and verify no pull is made 246 | futures.take().complete(null); 247 | assertThat(requestQueue.poll(1, SECONDS), is(nullValue())); 248 | assertThat(puller.outstandingRequests(), is(1)); 249 | assertThat(puller.outstandingMessages(), is(3)); 250 | 251 | // Complete handling of another message and verify that a pull request is made 252 | futures.take().complete(null); 253 | final Request r3 = requestQueue.take(); 254 | assertThat(puller.outstandingRequests(), is(2)); 255 | assertThat(puller.outstandingMessages(), is(2)); 256 | } 257 | 258 | @Test 259 | public void verifyCloseWillClosePubsubClient() throws Exception { 260 | puller = Puller.builder() 261 | .project(PROJECT) 262 | .subscription(SUBSCRIPTION) 263 | .pubsub(pubsub) 264 | .messageHandler(handler) 265 | .build(); 266 | puller.close(); 267 | verify(pubsub).close(); 268 | } 269 | 270 | private void setUpPubsubClient() { 271 | reset(pubsub); 272 | when(pubsub.pull(anyString(), anyString(), anyBoolean(), anyInt())) 273 | .thenAnswer(invocation -> { 274 | final String project = invocation.getArgument(0, String.class); 275 | final String subscription = invocation.getArgument(1, String.class); 276 | final boolean returnImmediately = invocation.getArgument(2, Boolean.class); 277 | final int maxMessages = invocation.getArgument(3, Integer.class); 278 | final String canonicalSubscription = Subscription.canonicalSubscription(project, subscription); 279 | final String uri = BASE_URI + canonicalSubscription + ":pull"; 280 | final RequestInfo requestInfo = RequestInfo.builder() 281 | .operation("pull") 282 | .method("POST") 283 | .uri(uri) 284 | .payloadSize(4711) 285 | .build(); 286 | final PubsubFuture> future = new PubsubFuture<>(requestInfo); 287 | requestQueue.add(new Request(future)); 288 | return future; 289 | }); 290 | } 291 | 292 | private static class Request { 293 | 294 | final PubsubFuture> future; 295 | 296 | Request(final PubsubFuture> future) { 297 | this.future = future; 298 | } 299 | } 300 | 301 | } 302 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/PushConfigTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.junit.Test; 40 | 41 | import java.util.Optional; 42 | 43 | import static org.hamcrest.Matchers.is; 44 | import static org.junit.Assert.assertThat; 45 | 46 | public class PushConfigTest { 47 | 48 | @Test 49 | public void testOf() throws Exception { 50 | final String pushEndpoint = "https://foo.bar/baz"; 51 | final PushConfig pushConfig = PushConfig.of(pushEndpoint); 52 | assertThat(pushConfig.pushEndpoint(), is(Optional.of(pushEndpoint))); 53 | } 54 | 55 | @Test 56 | public void testOfEmpty() throws Exception { 57 | final PushConfig pushConfig = PushConfig.of(); 58 | assertThat(pushConfig.pushEndpoint(), is(Optional.empty())); 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/ReceivedMessageTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import org.junit.Test; 40 | 41 | import static org.hamcrest.Matchers.is; 42 | import static org.junit.Assert.assertThat; 43 | 44 | public class ReceivedMessageTest { 45 | 46 | @Test 47 | public void testOfEncoded() throws Exception { 48 | final ReceivedMessage receivedMessage = ReceivedMessage.ofEncoded("a1", "hello world"); 49 | assertThat(receivedMessage.ackId(), is("a1")); 50 | assertThat(receivedMessage.message().data(), is(Message.encode("hello world"))); 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/SubscriptionTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.collect.ImmutableMap; 40 | 41 | import org.junit.Rule; 42 | import org.junit.Test; 43 | import org.junit.rules.ExpectedException; 44 | 45 | import java.io.IOException; 46 | import java.util.Arrays; 47 | 48 | import static org.hamcrest.Matchers.is; 49 | import static org.hamcrest.Matchers.nullValue; 50 | import static org.junit.Assert.assertThat; 51 | import static org.junit.Assert.assertTrue; 52 | 53 | public class SubscriptionTest { 54 | 55 | private static final String PROJECT = "test-project"; 56 | private static final String TOPIC = "test-topic"; 57 | private static final String SUBSCRIPTION = "test-subscription"; 58 | 59 | @Rule public ExpectedException exception = ExpectedException.none(); 60 | 61 | @Test 62 | public void testOf() throws Exception { 63 | final Subscription subscription = Subscription.of(PROJECT, SUBSCRIPTION, TOPIC); 64 | final String canonical = Subscription.canonicalSubscription(PROJECT, SUBSCRIPTION); 65 | assertThat(subscription.name(), is(canonical)); 66 | } 67 | 68 | @Test 69 | public void testOfCanonical() throws Exception { 70 | final String canonicalSubscription = Subscription.canonicalSubscription(PROJECT, SUBSCRIPTION); 71 | final String canonicalTopic = Topic.canonicalTopic(PROJECT, TOPIC); 72 | final Subscription subscription = Subscription.of(canonicalSubscription, canonicalTopic); 73 | assertThat(subscription.name(), is(canonicalSubscription)); 74 | } 75 | 76 | @Test 77 | public void testCanonicalTopic() throws Exception { 78 | final String canonical = Subscription.canonicalSubscription(PROJECT, SUBSCRIPTION); 79 | assertThat(canonical, is("projects/" + PROJECT + "/subscriptions/" + SUBSCRIPTION)); 80 | } 81 | 82 | @Test(expected = IllegalArgumentException.class) 83 | public void testValidateCanonicalTopic() throws Exception { 84 | Subscription.validateCanonicalSubscription("foo/bar/"); 85 | } 86 | 87 | @Test 88 | public void testBuilder() throws Exception { 89 | final String canonicalSubscription = Subscription.canonicalSubscription(PROJECT, SUBSCRIPTION); 90 | final SubscriptionBuilder builder = Subscription.builder(); 91 | assertThat(builder.name(), is(nullValue())); 92 | builder.name(canonicalSubscription); 93 | assertThat(builder.name(), is(canonicalSubscription)); 94 | final String canonicalTopic = Topic.canonicalTopic(PROJECT, TOPIC); 95 | builder.topic(canonicalTopic); 96 | assertThat(builder.topic(), is(canonicalTopic)); 97 | final Subscription subscription = builder.build(); 98 | assertThat(subscription.name(), is(canonicalSubscription)); 99 | assertThat(subscription.topic(), is(canonicalTopic)); 100 | } 101 | 102 | @Test 103 | public void testWriteJson() { 104 | final String canonicalSubscription = Subscription.canonicalSubscription(PROJECT, SUBSCRIPTION); 105 | final String canonicalTopic = Topic.canonicalTopic(PROJECT, TOPIC); 106 | final byte[] expected = Json.write(ImmutableMap.of("name", canonicalSubscription, 107 | "topic", canonicalTopic)); 108 | final byte[] json = Json.write(Subscription.of(PROJECT, SUBSCRIPTION, TOPIC)); 109 | assertTrue(Arrays.equals(json, expected)); 110 | } 111 | 112 | @Test 113 | public void testReadJson() throws IOException { 114 | final Subscription expected = Subscription.of(PROJECT, SUBSCRIPTION, TOPIC); 115 | final byte[] json = Json.write(ImmutableMap.of("name", expected.name(), "topic", expected.topic())); 116 | final Subscription subscription = Json.read(json, Subscription.class); 117 | } 118 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/TopicTest.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client; 38 | 39 | import com.google.common.collect.ImmutableMap; 40 | 41 | import org.junit.Rule; 42 | import org.junit.Test; 43 | import org.junit.rules.ExpectedException; 44 | 45 | import java.io.IOException; 46 | import java.util.Arrays; 47 | 48 | import static org.hamcrest.Matchers.is; 49 | import static org.hamcrest.Matchers.nullValue; 50 | import static org.junit.Assert.assertThat; 51 | import static org.junit.Assert.assertTrue; 52 | 53 | public class TopicTest { 54 | 55 | private static final String PROJECT = "test-project"; 56 | private static final String TOPIC = "test-topic"; 57 | 58 | @Rule public ExpectedException exception = ExpectedException.none(); 59 | 60 | @Test 61 | public void testOf() throws Exception { 62 | final Topic topic = Topic.of(PROJECT, TOPIC); 63 | final String canonical = Topic.canonicalTopic(PROJECT, TOPIC); 64 | assertThat(topic.name(), is(canonical)); 65 | } 66 | 67 | @Test 68 | public void testOfCanonical() throws Exception { 69 | final String canonical = Topic.canonicalTopic(PROJECT, TOPIC); 70 | final Topic topic = Topic.of(canonical); 71 | assertThat(topic.name(), is(canonical)); 72 | } 73 | 74 | @Test 75 | public void testCanonicalTopic() throws Exception { 76 | final String canonical = Topic.canonicalTopic(PROJECT, TOPIC); 77 | assertThat(canonical, is("projects/" + PROJECT + "/topics/" + TOPIC)); 78 | } 79 | 80 | @Test(expected = IllegalArgumentException.class) 81 | public void testValidateCanonicalTopic() throws Exception { 82 | Topic.validateCanonicalTopic("foo/bar/"); 83 | } 84 | 85 | @Test 86 | public void testBuilder() throws Exception { 87 | final String canonicalTopic = Topic.canonicalTopic(PROJECT, TOPIC); 88 | final TopicBuilder builder = Topic.builder(); 89 | assertThat(builder.name(), is(nullValue())); 90 | builder.name(canonicalTopic); 91 | assertThat(builder.name(), is(canonicalTopic)); 92 | final Topic topic = builder.build(); 93 | assertThat(topic.name(), is(canonicalTopic)); 94 | } 95 | 96 | @Test 97 | public void testWriteJson() { 98 | final String canonicalTopic = Topic.canonicalTopic(PROJECT, TOPIC); 99 | final byte[] expected = Json.write(ImmutableMap.of("name", canonicalTopic)); 100 | final byte[] json = Json.write(Topic.of(PROJECT, TOPIC)); 101 | assertTrue(Arrays.equals(json, expected)); 102 | } 103 | 104 | @Test 105 | public void testReadJson() throws IOException { 106 | final Topic expected = Topic.of(PROJECT, TOPIC); 107 | final byte[] json = Json.write(ImmutableMap.of("name", expected.name())); 108 | final Topic topic = Json.read(json, Topic.class); 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/example/PublisherExample.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.example; 38 | 39 | import com.spotify.google.cloud.pubsub.client.Message; 40 | import com.spotify.google.cloud.pubsub.client.Publisher; 41 | import com.spotify.google.cloud.pubsub.client.Pubsub; 42 | 43 | import java.util.Collections; 44 | import java.util.concurrent.ExecutionException; 45 | 46 | public class PublisherExample { 47 | 48 | public static void main(String[] args) throws ExecutionException, InterruptedException { 49 | final Pubsub pubsub = Pubsub.builder() 50 | .build(); 51 | 52 | final Publisher publisher = Publisher.builder() 53 | .pubsub(pubsub) 54 | .project("my-google-cloud-project") 55 | .concurrency(128) 56 | .build(); 57 | 58 | // A never ending stream of messages... 59 | final Iterable messageStream = incomingMessages(); 60 | 61 | // Publish incoming messages 62 | messageStream.forEach(m -> publisher.publish(m.topic, m.message)); 63 | } 64 | 65 | private static Iterable incomingMessages() { 66 | // Some never ending stream of messages... 67 | return Collections.emptyList(); 68 | } 69 | 70 | static class MessageAndTopic { 71 | 72 | String topic; 73 | Message message; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/example/PubsubExample.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.example; 38 | 39 | import com.spotify.google.cloud.pubsub.client.Message; 40 | import com.spotify.google.cloud.pubsub.client.Pubsub; 41 | import com.spotify.google.cloud.pubsub.client.ReceivedMessage; 42 | 43 | import java.io.UnsupportedEncodingException; 44 | import java.util.List; 45 | import java.util.concurrent.ExecutionException; 46 | import java.util.stream.Collectors; 47 | 48 | import static com.spotify.google.cloud.pubsub.client.Message.encode; 49 | import static java.util.Arrays.asList; 50 | 51 | public class PubsubExample { 52 | 53 | public static void main(String[] args) throws ExecutionException, InterruptedException, UnsupportedEncodingException { 54 | final Pubsub pubsub = Pubsub.create(); 55 | 56 | // Create a topic 57 | pubsub.createTopic("my-google-cloud-project", "the-topic").get(); 58 | 59 | // Create a subscription 60 | pubsub.createSubscription("my-google-cloud-project", "the-subscription-name", "the-topic").get(); 61 | 62 | // Create a batch of messages 63 | final List messages = asList( 64 | Message.builder() 65 | .attributes("type", "foo") 66 | .data(encode("hello foo")) 67 | .build(), 68 | Message.builder() 69 | .attributes("type", "bar") 70 | .data(encode("hello foo")) 71 | .build()); 72 | 73 | // Publish the messages 74 | final List messageIds = pubsub.publish("my-google-cloud-project", "the-topic", messages).get(); 75 | System.out.println("Message IDs: " + messageIds); 76 | 77 | // Pull the message 78 | final List received = pubsub.pull("my-google-cloud-project", "the-subscription").get(); 79 | System.out.println("Received Messages: " + received); 80 | 81 | // Ack the received messages 82 | final List ackIds = received.stream().map(ReceivedMessage::ackId).collect(Collectors.toList()); 83 | pubsub.acknowledge("my-google-cloud-project", "the-subscription", ackIds).get(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/example/PullerExample.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2016 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.example; 38 | 39 | import com.spotify.google.cloud.pubsub.client.Pubsub; 40 | import com.spotify.google.cloud.pubsub.client.Puller; 41 | 42 | import java.util.concurrent.CompletableFuture; 43 | import java.util.concurrent.ExecutionException; 44 | 45 | import static com.spotify.google.cloud.pubsub.client.Puller.MessageHandler; 46 | import static com.spotify.google.cloud.pubsub.client.Puller.builder; 47 | 48 | public class PullerExample { 49 | 50 | public static void main(String[] args) throws ExecutionException, InterruptedException { 51 | final Pubsub pubsub = Pubsub.builder() 52 | .build(); 53 | 54 | final MessageHandler handler = (puller, subscription, message, ackId) -> { 55 | System.out.println("got message: " + message); 56 | return CompletableFuture.completedFuture(ackId); 57 | }; 58 | 59 | final Puller puller = builder() 60 | .pubsub(pubsub) 61 | .project("my-google-cloud-project") 62 | .subscription("my-subscription") 63 | .concurrency(32) 64 | .messageHandler(handler) 65 | .build(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/EndToEndBenchmark.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 40 | import com.google.api.services.pubsub.PubsubScopes; 41 | 42 | import com.spotify.google.cloud.pubsub.client.Message; 43 | import com.spotify.google.cloud.pubsub.client.Publisher; 44 | import com.spotify.google.cloud.pubsub.client.Pubsub; 45 | import com.spotify.google.cloud.pubsub.client.ReceivedMessage; 46 | import com.spotify.logging.LoggingConfigurator; 47 | 48 | import java.io.FileInputStream; 49 | import java.io.IOException; 50 | import java.util.List; 51 | import java.util.concurrent.CompletableFuture; 52 | import java.util.concurrent.ExecutionException; 53 | import java.util.concurrent.ThreadLocalRandom; 54 | import java.util.function.Supplier; 55 | import java.util.stream.IntStream; 56 | import java.util.zip.Deflater; 57 | 58 | import static com.spotify.google.cloud.pubsub.client.integration.Util.nonGcmCiphers; 59 | import static com.spotify.logging.LoggingConfigurator.Level.WARN; 60 | import static java.util.stream.Collectors.toList; 61 | 62 | public class EndToEndBenchmark { 63 | 64 | private static final int PUBLISHER_CONCURRENCY = 128; 65 | private static final int PULLER_CONCURRENCY = 128; 66 | 67 | private static final int MESSAGE_SIZE = 512; 68 | 69 | public static void main(final String... args) throws IOException, ExecutionException, InterruptedException { 70 | 71 | final String project = Util.defaultProject(); 72 | 73 | GoogleCredential credential; 74 | 75 | // Use credentials from file if available 76 | try { 77 | credential = GoogleCredential 78 | .fromStream(new FileInputStream("credentials.json")) 79 | .createScoped(PubsubScopes.all()); 80 | } catch (IOException e) { 81 | credential = GoogleCredential.getApplicationDefault() 82 | .createScoped(PubsubScopes.all()); 83 | } 84 | 85 | final Pubsub pubsub = Pubsub.builder() 86 | .credential(credential) 87 | .compressionLevel(Deflater.BEST_SPEED) 88 | .enabledCipherSuites(nonGcmCiphers()) 89 | .build(); 90 | 91 | final Publisher publisher = Publisher.builder() 92 | .pubsub(pubsub) 93 | .concurrency(PUBLISHER_CONCURRENCY) 94 | .project(project) 95 | .build(); 96 | 97 | LoggingConfigurator.configureDefaults("benchmark", WARN); 98 | 99 | final String topic = "test-" + Long.toHexString(ThreadLocalRandom.current().nextLong()); 100 | final String subscription = "test-" + Long.toHexString(ThreadLocalRandom.current().nextLong()); 101 | 102 | pubsub.createTopic(project, topic).get(); 103 | pubsub.createSubscription(project, subscription, topic).get(); 104 | 105 | final List payloads = IntStream.range(0, 1024) 106 | .mapToObj(i -> { 107 | final StringBuilder s = new StringBuilder(); 108 | while (s.length() < MESSAGE_SIZE) { 109 | s.append(ThreadLocalRandom.current().nextInt()); 110 | } 111 | return Message.encode(s.toString()); 112 | }) 113 | .collect(toList()); 114 | final int payloadIxMask = 1024 - 1; 115 | 116 | final Supplier generator = () -> Message.builder() 117 | .data(payloads.get(ThreadLocalRandom.current().nextInt() & payloadIxMask)) 118 | .putAttribute("ts", Long.toHexString(System.nanoTime())) 119 | .build(); 120 | 121 | final ProgressMeter meter = new ProgressMeter(); 122 | final ProgressMeter.Metric publishes = meter.group("operations").metric("publishes", "messages"); 123 | final ProgressMeter.Metric receives = meter.group("operations").metric("receives", "messages"); 124 | 125 | for (int i = 0; i < 100000; i++) { 126 | publish(publisher, generator, topic, publishes); 127 | } 128 | 129 | // Pull concurrently and (asynchronously) publish a new message for every message received 130 | for (int i = 0; i < PULLER_CONCURRENCY; i++) { 131 | pull(project, pubsub, subscription, receives, () -> publish(publisher, generator, topic, publishes)); 132 | } 133 | } 134 | 135 | private static void publish(final Publisher publisher, final Supplier generator, 136 | final String topic, final ProgressMeter.Metric publishes) { 137 | final Message message = generator.get(); 138 | final CompletableFuture future = publisher.publish(topic, message); 139 | final long start = System.nanoTime(); 140 | future.whenComplete((s, ex) -> { 141 | if (ex != null) { 142 | ex.printStackTrace(); 143 | return; 144 | } 145 | final long end = System.nanoTime(); 146 | final long latency = end - start; 147 | publishes.inc(latency); 148 | }); 149 | } 150 | 151 | private static void pull(final String project, final Pubsub pubsub, final String subscription, 152 | final ProgressMeter.Metric receives, final Runnable callback) { 153 | pubsub.pull(project, subscription, false, 1000) 154 | .whenComplete((messages, ex) -> { 155 | if (ex != null) { 156 | ex.printStackTrace(); 157 | return; 158 | } 159 | // Immediately kick off another pull 160 | pull(project, pubsub, subscription, receives, callback); 161 | 162 | // Ack received messages 163 | final String[] ackIds = messages.stream().map(ReceivedMessage::ackId).toArray(String[]::new); 164 | pubsub.acknowledge(project, subscription, ackIds); 165 | 166 | // Account for and call callback for each received message 167 | for (final ReceivedMessage message : messages) { 168 | final String tsHex = message.message().attributes().get("ts"); 169 | final long tsNanos = Long.valueOf(tsHex, 16); 170 | final long latencyNanos = System.nanoTime() - tsNanos; 171 | receives.inc(latencyNanos); 172 | callback.run(); 173 | } 174 | }); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/GoogleClientPullBenchmark.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 40 | import com.google.api.client.googleapis.util.Utils; 41 | import com.google.api.services.pubsub.Pubsub; 42 | import com.google.api.services.pubsub.PubsubScopes; 43 | import com.google.api.services.pubsub.model.AcknowledgeRequest; 44 | import com.google.api.services.pubsub.model.PullRequest; 45 | import com.google.api.services.pubsub.model.PullResponse; 46 | import com.google.api.services.pubsub.model.ReceivedMessage; 47 | import com.google.common.util.concurrent.ListeningExecutorService; 48 | import com.google.common.util.concurrent.MoreExecutors; 49 | 50 | import com.spotify.google.cloud.pubsub.client.Subscription; 51 | import com.spotify.logging.LoggingConfigurator; 52 | 53 | import java.io.FileInputStream; 54 | import java.io.IOException; 55 | import java.util.List; 56 | import java.util.concurrent.ExecutionException; 57 | import java.util.concurrent.Executors; 58 | import java.util.stream.Collectors; 59 | 60 | import static com.spotify.logging.LoggingConfigurator.Level.WARN; 61 | 62 | public class GoogleClientPullBenchmark { 63 | 64 | private static final int PULLER_CONCURRENCY = 128; 65 | 66 | public static void main(final String... args) throws IOException, ExecutionException, InterruptedException { 67 | 68 | final String project = Util.defaultProject(); 69 | 70 | GoogleCredential credential; 71 | 72 | // Use credentials from file if available 73 | try { 74 | credential = GoogleCredential 75 | .fromStream(new FileInputStream("credentials.json")) 76 | .createScoped(PubsubScopes.all()); 77 | } catch (IOException e) { 78 | credential = GoogleCredential.getApplicationDefault() 79 | .createScoped(PubsubScopes.all()); 80 | } 81 | 82 | LoggingConfigurator.configureDefaults("benchmark", WARN); 83 | 84 | final String subscription = System.getenv("GOOGLE_PUBSUB_SUBSCRIPTION"); 85 | if (subscription == null) { 86 | System.err.println("Please specify a subscription using the GOOGLE_PUBSUB_SUBSCRIPTION environment variable."); 87 | System.exit(1); 88 | } 89 | 90 | System.out.println("Consuming from GOOGLE_PUBSUB_SUBSCRIPTION='" + subscription + "'"); 91 | 92 | final Pubsub pubsub = new Pubsub.Builder(Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), credential) 93 | .setApplicationName("pull-benchmark") 94 | .build(); 95 | 96 | final Pubsub.Projects.Subscriptions subscriptions = pubsub.projects().subscriptions(); 97 | 98 | final String canonicalSubscription = Subscription.canonicalSubscription(project, subscription); 99 | 100 | final ProgressMeter meter = new ProgressMeter(); 101 | final ProgressMeter.Metric receives = meter.group("operations").metric("receives", "messages"); 102 | 103 | final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 104 | 105 | // Pull concurrently 106 | for (int i = 0; i < PULLER_CONCURRENCY; i++) { 107 | pull(subscriptions, canonicalSubscription, receives, executor); 108 | } 109 | } 110 | 111 | private static void pull(final Pubsub.Projects.Subscriptions subscriptions, 112 | final String subscription, final ProgressMeter.Metric receives, 113 | final ListeningExecutorService executor) { 114 | 115 | final long start = System.nanoTime(); 116 | 117 | executor.submit(() -> { 118 | final PullRequest pullRequest = new PullRequest().setMaxMessages(1000).setReturnImmediately(false); 119 | try { 120 | // Blocking pull 121 | final PullResponse response = subscriptions.pull(subscription, pullRequest).execute(); 122 | final long end = System.nanoTime(); 123 | final long latency = end - start; 124 | final List messages = response.getReceivedMessages(); 125 | receives.add(messages.size(), latency); 126 | 127 | // Ack received messages 128 | final List ackIds = messages.stream().map(ReceivedMessage::getAckId).collect(Collectors.toList()); 129 | executor.submit(() -> { 130 | final AcknowledgeRequest ackRequest = new AcknowledgeRequest().setAckIds(ackIds); 131 | try { 132 | subscriptions.acknowledge(subscription, ackRequest).execute(); 133 | } catch (IOException e) { 134 | e.printStackTrace(); 135 | } 136 | }); 137 | } catch (IOException e) { 138 | e.printStackTrace(); 139 | } 140 | 141 | // Kick off another pull 142 | pull(subscriptions, subscription, receives, executor); 143 | }); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/ProgressMeter.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.common.collect.Lists; 40 | import com.google.common.collect.Maps; 41 | import com.google.common.collect.Queues; 42 | import com.google.common.primitives.Ints; 43 | 44 | import java.text.DateFormat; 45 | import java.text.SimpleDateFormat; 46 | import java.util.Collection; 47 | import java.util.Collections; 48 | import java.util.Date; 49 | import java.util.Deque; 50 | import java.util.List; 51 | import java.util.Map; 52 | import java.util.concurrent.ConcurrentMap; 53 | import java.util.concurrent.TimeUnit; 54 | import java.util.concurrent.atomic.LongAdder; 55 | 56 | import static com.google.common.base.Optional.fromNullable; 57 | import static java.lang.Math.max; 58 | import static java.lang.String.format; 59 | import static java.lang.System.out; 60 | import static java.util.Collections.unmodifiableMap; 61 | 62 | /** 63 | * A simple progress meter. Prints average throughput and latencies for a set of metrics created 64 | * with {@link #group(String)} and {@link MetricGroup#metric(Object, String)}. 65 | */ 66 | class ProgressMeter { 67 | 68 | private static final int AVERAGE_WINDOW = 10; 69 | public static final int NANOS_PER_S = 1000000000; 70 | public static final double NANOS_PER_MS = 1000000.d; 71 | private final long interval = 1000; 72 | private final Thread printer; 73 | 74 | private volatile boolean run = true; 75 | private volatile Map groups = Maps.newLinkedHashMap(); 76 | 77 | /** 78 | * Create a new progress meter. 79 | */ 80 | public ProgressMeter() { 81 | printer = new ProgressPrinter(); 82 | } 83 | 84 | /** 85 | * Stop the progress meter. 86 | */ 87 | public void stop() { 88 | run = false; 89 | printer.interrupt(); 90 | } 91 | 92 | public MetricGroup group(final String name) { 93 | MetricGroup group = groups.get(name); 94 | if (group == null) { 95 | synchronized (this) { 96 | group = groups.get(name); 97 | if (group == null) { 98 | final Map newGroups = Maps.newLinkedHashMap(groups); 99 | group = new MetricGroup(); 100 | newGroups.put(name, group); 101 | groups = unmodifiableMap(newGroups); 102 | } 103 | } 104 | } 105 | return group; 106 | } 107 | 108 | private static class Delta { 109 | 110 | private final long ops; 111 | private final long interval; 112 | private final long latency; 113 | 114 | Delta(final long ops, final long interval, final long latency) { 115 | this.ops = ops; 116 | this.interval = interval; 117 | this.latency = latency; 118 | } 119 | } 120 | 121 | public class Metric implements Comparable { 122 | 123 | private final Object[] name; 124 | private final String unit; 125 | private final LongAdder totalLatency = new LongAdder(); 126 | private final LongAdder totalOperations = new LongAdder(); 127 | private final Deque deltas = Queues.newArrayDeque(); 128 | private long prevTotalOperations = 0; 129 | private long prevTimestamp = System.nanoTime(); 130 | private long prevTotalLatency = 0; 131 | 132 | private Metric(final Object[] name, final String unit) { 133 | this.name = name; 134 | this.unit = unit; 135 | } 136 | 137 | private void print(final Table table) { 138 | final long now = System.nanoTime(); 139 | final long totalOperations = this.totalOperations.longValue(); 140 | final long totalLatency = this.totalLatency.longValue(); 141 | 142 | final long deltaOps = totalOperations - prevTotalOperations; 143 | final long deltaTime = now - prevTimestamp; 144 | final long deltaLatency = totalLatency - prevTotalLatency; 145 | 146 | deltas.add(new Delta(deltaOps, deltaTime, deltaLatency)); 147 | 148 | if (deltas.size() > AVERAGE_WINDOW) { 149 | deltas.pop(); 150 | } 151 | 152 | long windowOps = 0; 153 | long windowInterval = 0; 154 | long windowLatency = 0; 155 | 156 | for (final Delta d : deltas) { 157 | windowInterval += d.interval; 158 | windowOps += d.ops; 159 | windowLatency += d.latency; 160 | } 161 | 162 | // TODO (dano): this should all be thrown away and replaced with e.g. HdrHistogram so we can 163 | // TODO (dano): get percentiles instead of just averages. 164 | 165 | final long operations = deltaTime == 0 ? 0 : NANOS_PER_S * deltaOps / deltaTime; 166 | final long avgOps = windowInterval == 0 ? 0 : NANOS_PER_S * windowOps / windowInterval; 167 | final double latency = deltaOps == 0 ? 0 : deltaLatency / (NANOS_PER_MS * deltaOps); 168 | final double avgLatency = windowOps == 0 ? 0 : windowLatency / (NANOS_PER_MS * windowOps); 169 | 170 | table.row(row(name, " ", 171 | format("%,12d", operations), 172 | " (", format("%,9d", avgOps), " avg) ", unit, "/s ", 173 | format("%,12.3f", latency), 174 | " (", format("%,12.3f", avgLatency), " avg)", " ms latency ", 175 | format("%,12d", totalOperations), " total")); 176 | 177 | prevTotalOperations = totalOperations; 178 | prevTimestamp = now; 179 | prevTotalLatency = totalLatency; 180 | } 181 | 182 | public Object[] row(Object[] first, Object... second) { 183 | Object[] result = new Object[first.length * 2 + second.length]; 184 | for (int i = 0; i < first.length; i++) { 185 | result[i * 2] = first[i]; 186 | result[i * 2 + 1] = " "; 187 | } 188 | System.arraycopy(second, 0, result, first.length * 2, second.length); 189 | return result; 190 | } 191 | 192 | /** 193 | * Increase this metric. 194 | * 195 | * @param delta The amount to increase. 196 | * @param latency The total latency for the metric delta, if applicable. 197 | */ 198 | public void add(final long delta, final long latency) { 199 | this.totalOperations.add(delta); 200 | this.totalLatency.add(latency); 201 | } 202 | 203 | /** 204 | * Increase this metric by one. 205 | * 206 | * @param latency The latency of the event. 207 | */ 208 | public void inc(final long latency) { 209 | add(1, latency); 210 | } 211 | 212 | /** 213 | * Used to sort metrics by their names. 214 | */ 215 | @Override 216 | public int compareTo(final Metric o) { 217 | for (int i = 0; i < name.length; i++) { 218 | if (i > o.name.length) { 219 | return 1; 220 | } 221 | final int d = name[i].toString().compareTo(o.name[i].toString()); 222 | if (d != 0) { 223 | return d; 224 | } 225 | } 226 | 227 | return 0; 228 | } 229 | } 230 | 231 | private class ProgressPrinter extends Thread { 232 | 233 | private final long start = System.nanoTime(); 234 | private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 235 | 236 | private ProgressPrinter() { 237 | setDaemon(true); 238 | start(); 239 | } 240 | 241 | public void run() { 242 | while (run) { 243 | try { 244 | Thread.sleep(interval); 245 | } catch (InterruptedException e) { 246 | continue; 247 | } 248 | print(); 249 | } 250 | } 251 | 252 | private void print() { 253 | final Date date = new Date(); 254 | final Table table = new Table(); 255 | final long seconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start); 256 | table.header(dateFormat.format(date) + " (" + seconds + " s)"); 257 | for (final MetricGroup group : groups.values()) { 258 | group.print(table); 259 | } 260 | table.print(); 261 | out.println(); 262 | } 263 | } 264 | 265 | public class MetricGroup { 266 | 267 | private ConcurrentMap metrics = Maps.newConcurrentMap(); 268 | 269 | /** 270 | * Create a new metric tracked by this progress meter. 271 | * 272 | * @param unit The entity unit, being measured, e.g. "requests", "messages", "operations", etc. 273 | * @return A new {@link Metric}. 274 | */ 275 | public Metric metric(final Object key, final String unit, final Object... name) { 276 | final Metric metric = metrics.get(key); 277 | if (metric == null) { 278 | final Metric newMetric = new Metric(name, unit); 279 | final Metric existingMetric = metrics.putIfAbsent(key, newMetric); 280 | return fromNullable(existingMetric).or(newMetric); 281 | } 282 | return metric; 283 | } 284 | 285 | public Metric metric(final Object key, final String unit) { 286 | return metric(key, unit, key); 287 | } 288 | 289 | private void print(final Table table) { 290 | for (final Metric metric : sorted(metrics.values())) { 291 | metric.print(table); 292 | } 293 | } 294 | 295 | private > List sorted(final Collection values) { 296 | final List list = Lists.newArrayList(values); 297 | Collections.sort(list); 298 | return list; 299 | } 300 | } 301 | 302 | private class Table { 303 | 304 | private int[] columns = new int[0]; 305 | private final List rows = Lists.newArrayList(); 306 | private String header; 307 | 308 | public void row(final Object... row) { 309 | columns = Ints.ensureCapacity(columns, row.length, row.length); 310 | for (int i = 0; i < row.length; i++) { 311 | row[i] = row[i].toString(); 312 | columns[i] = max(columns[i], row[i].toString().length()); 313 | } 314 | rows.add(row); 315 | } 316 | 317 | public void print() { 318 | final StringBuilder builder = new StringBuilder(); 319 | if (header != null) { 320 | builder.append(header); 321 | builder.append('\n'); 322 | for (int column : columns) { 323 | for (int i = 0; i < column; i++) { 324 | builder.append('-'); 325 | } 326 | } 327 | builder.append('\n'); 328 | out.print(builder.toString()); 329 | } 330 | 331 | for (final Object[] row : rows) { 332 | for (int i = 0; i < row.length; i++) { 333 | final String cell = row[i].toString(); 334 | final int padding = columns[i] - cell.length(); 335 | for (int j = 0; j < padding; j++) { 336 | out.print(' '); 337 | } 338 | out.print(cell); 339 | } 340 | out.println(); 341 | } 342 | } 343 | 344 | public void header(final String header) { 345 | this.header = header; 346 | } 347 | } 348 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/PublisherBenchmark.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 40 | import com.google.api.services.pubsub.PubsubScopes; 41 | import com.google.common.util.concurrent.Futures; 42 | 43 | import com.spotify.google.cloud.pubsub.client.Message; 44 | import com.spotify.google.cloud.pubsub.client.Publisher; 45 | import com.spotify.google.cloud.pubsub.client.Pubsub; 46 | import com.spotify.logging.LoggingConfigurator; 47 | 48 | import java.io.FileInputStream; 49 | import java.io.IOException; 50 | import java.util.List; 51 | import java.util.concurrent.CompletableFuture; 52 | import java.util.concurrent.ThreadLocalRandom; 53 | import java.util.stream.IntStream; 54 | import java.util.zip.Deflater; 55 | 56 | import static com.spotify.google.cloud.pubsub.client.integration.Util.TEST_NAME_PREFIX; 57 | import static com.spotify.google.cloud.pubsub.client.integration.Util.nonGcmCiphers; 58 | import static com.spotify.logging.LoggingConfigurator.Level.WARN; 59 | import static java.util.stream.Collectors.toList; 60 | 61 | public class PublisherBenchmark { 62 | 63 | private static final int MESSAGE_SIZE = 512; 64 | 65 | public static void main(final String... args) throws IOException { 66 | 67 | final String project = Util.defaultProject(); 68 | 69 | GoogleCredential credential; 70 | 71 | // Use credentials from file if available 72 | try { 73 | credential = GoogleCredential 74 | .fromStream(new FileInputStream("credentials.json")) 75 | .createScoped(PubsubScopes.all()); 76 | } catch (IOException e) { 77 | credential = GoogleCredential.getApplicationDefault() 78 | .createScoped(PubsubScopes.all()); 79 | } 80 | 81 | final Pubsub pubsub = Pubsub.builder() 82 | .credential(credential) 83 | .compressionLevel(Deflater.BEST_SPEED) 84 | .enabledCipherSuites(nonGcmCiphers()) 85 | .build(); 86 | 87 | final Publisher publisher = Publisher.builder() 88 | .pubsub(pubsub) 89 | .concurrency(128) 90 | .project(project) 91 | .build(); 92 | 93 | LoggingConfigurator.configureDefaults("benchmark", WARN); 94 | 95 | final String topicPrefix = TEST_NAME_PREFIX + ThreadLocalRandom.current().nextInt(); 96 | 97 | final List topics = IntStream.range(0, 100) 98 | .mapToObj(i -> topicPrefix + "-" + i) 99 | .collect(toList()); 100 | 101 | topics.stream() 102 | .map(topic -> pubsub.createTopic(project, topic)) 103 | .collect(toList()) 104 | .forEach(Futures::getUnchecked); 105 | 106 | final List messages = IntStream.range(0, 1000) 107 | .mapToObj(i -> { 108 | final StringBuilder s = new StringBuilder(); 109 | while (s.length() < MESSAGE_SIZE) { 110 | s.append(ThreadLocalRandom.current().nextInt()); 111 | } 112 | return Message.ofEncoded(s.toString()); 113 | }) 114 | .collect(toList()); 115 | 116 | final ProgressMeter meter = new ProgressMeter(); 117 | final ProgressMeter.Metric requests = meter.group("operations").metric("publishes", "messages"); 118 | 119 | for (int i = 0; i < 100000; i++) { 120 | benchSend(publisher, messages, topics, requests); 121 | } 122 | } 123 | 124 | private static void benchSend(final Publisher publisher, final List messages, 125 | final List topics, final ProgressMeter.Metric requests) { 126 | final String topic = topics.get(ThreadLocalRandom.current().nextInt(topics.size())); 127 | final Message message = messages.get(ThreadLocalRandom.current().nextInt(messages.size())); 128 | final CompletableFuture future = publisher.publish(topic, message); 129 | final long start = System.nanoTime(); 130 | future.whenComplete((s, ex) -> { 131 | if (ex != null) { 132 | ex.printStackTrace(); 133 | return; 134 | } 135 | final long end = System.nanoTime(); 136 | final long latency = end - start; 137 | requests.inc(latency); 138 | benchSend(publisher, messages, topics, requests); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/PublisherIT.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.common.util.concurrent.Futures; 40 | 41 | import com.spotify.google.cloud.pubsub.client.Message; 42 | import com.spotify.google.cloud.pubsub.client.Publisher; 43 | import com.spotify.google.cloud.pubsub.client.Pubsub; 44 | 45 | import org.junit.After; 46 | import org.junit.Before; 47 | import org.junit.Test; 48 | 49 | import java.io.UnsupportedEncodingException; 50 | import java.util.List; 51 | import java.util.concurrent.CompletableFuture; 52 | import java.util.concurrent.ExecutionException; 53 | import java.util.concurrent.ThreadLocalRandom; 54 | 55 | import static com.spotify.google.cloud.pubsub.client.integration.Util.TEST_NAME_PREFIX; 56 | import static java.lang.System.out; 57 | import static java.util.stream.Collectors.toList; 58 | import static java.util.stream.IntStream.range; 59 | 60 | /** 61 | * Tests publishing to the real Google Cloud Pub/Sub service. 62 | */ 63 | public class PublisherIT { 64 | 65 | private final String PROJECT = Util.defaultProject(); 66 | 67 | private final String TOPIC = TEST_NAME_PREFIX + ThreadLocalRandom.current().nextLong(); 68 | 69 | private Pubsub pubsub; 70 | private Publisher publisher; 71 | 72 | @Before 73 | public void setUp() throws ExecutionException, InterruptedException { 74 | pubsub = Pubsub.builder() 75 | .build(); 76 | publisher = Publisher.builder() 77 | .concurrency(20) 78 | .pubsub(pubsub) 79 | .project(PROJECT) 80 | .build(); 81 | pubsub.createTopic(PROJECT, TOPIC).get(); 82 | } 83 | 84 | @After 85 | public void tearDown() throws ExecutionException, InterruptedException { 86 | if (publisher != null) { 87 | publisher.close(); 88 | } 89 | if (pubsub != null) { 90 | pubsub.deleteTopic(PROJECT, TOPIC).exceptionally(t -> null).get(); 91 | pubsub.close(); 92 | } 93 | } 94 | 95 | @Test 96 | public void testPublish() 97 | throws UnsupportedEncodingException, ExecutionException, InterruptedException { 98 | final Message message = Message.ofEncoded("hello world"); 99 | 100 | final List> futures = range(0, 10) 101 | .mapToObj(i -> publisher.publish(TOPIC, message)) 102 | .collect(toList()); 103 | 104 | futures.stream() 105 | .map(Futures::getUnchecked) 106 | .forEach(id -> out.println("message id: " + id)); 107 | } 108 | } -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/PullerBenchmark.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 40 | import com.google.api.services.pubsub.PubsubScopes; 41 | 42 | import com.spotify.google.cloud.pubsub.client.Message; 43 | import com.spotify.google.cloud.pubsub.client.Pubsub; 44 | import com.spotify.google.cloud.pubsub.client.Puller; 45 | import com.spotify.logging.LoggingConfigurator; 46 | 47 | import java.io.FileInputStream; 48 | import java.io.IOException; 49 | import java.util.concurrent.CompletableFuture; 50 | import java.util.concurrent.CompletionStage; 51 | import java.util.concurrent.ExecutionException; 52 | import java.util.zip.Deflater; 53 | 54 | import static com.spotify.logging.LoggingConfigurator.Level.WARN; 55 | 56 | public class PullerBenchmark { 57 | 58 | private static final int PULLER_CONCURRENCY = 128; 59 | 60 | public static void main(final String... args) throws IOException, ExecutionException, InterruptedException { 61 | 62 | final String project = Util.defaultProject(); 63 | 64 | GoogleCredential credential; 65 | 66 | // Use credentials from file if available 67 | try { 68 | credential = GoogleCredential 69 | .fromStream(new FileInputStream("credentials.json")) 70 | .createScoped(PubsubScopes.all()); 71 | } catch (IOException e) { 72 | credential = GoogleCredential.getApplicationDefault() 73 | .createScoped(PubsubScopes.all()); 74 | } 75 | 76 | final Pubsub pubsub = Pubsub.builder() 77 | .credential(credential) 78 | .compressionLevel(Deflater.BEST_SPEED) 79 | .enabledCipherSuites(Util.nonGcmCiphers()) 80 | .build(); 81 | 82 | LoggingConfigurator.configureDefaults("benchmark", WARN); 83 | 84 | final String subscription = System.getenv("GOOGLE_PUBSUB_SUBSCRIPTION"); 85 | if (subscription == null) { 86 | System.err.println("Please specify a subscription using the GOOGLE_PUBSUB_SUBSCRIPTION environment variable."); 87 | System.exit(1); 88 | } 89 | 90 | System.out.println("Consuming from GOOGLE_PUBSUB_SUBSCRIPTION='" + subscription + "'"); 91 | 92 | final ProgressMeter meter = new ProgressMeter(); 93 | final ProgressMeter.Metric receives = meter.group("operations").metric("receives", "messages"); 94 | 95 | final Puller puller = Puller.builder() 96 | .pubsub(pubsub) 97 | .batchSize(1000) 98 | .concurrency(PULLER_CONCURRENCY) 99 | .project(project) 100 | .subscription(subscription) 101 | .messageHandler(new Handler(receives)) 102 | .build(); 103 | 104 | while (true) { 105 | Thread.sleep(1000); 106 | System.out.println("outstanding messages: " + puller.outstandingMessages()); 107 | System.out.println("outstanding requests: " + puller.outstandingRequests()); 108 | } 109 | } 110 | 111 | 112 | private static class Handler implements Puller.MessageHandler { 113 | 114 | private final ProgressMeter.Metric receives; 115 | 116 | public Handler(final ProgressMeter.Metric receives) { 117 | 118 | this.receives = receives; 119 | } 120 | 121 | @Override 122 | public CompletionStage handleMessage(final Puller puller, final String subscription, 123 | final Message message, final String ackId) { 124 | receives.inc(0); 125 | return CompletableFuture.completedFuture(ackId); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/google/cloud/pubsub/client/integration/Util.java: -------------------------------------------------------------------------------- 1 | /*- 2 | * -\-\- 3 | * async-google-pubsub-client 4 | * -- 5 | * Copyright (C) 2016 - 2017 Spotify AB 6 | * -- 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * -/-/- 19 | */ 20 | 21 | /* 22 | * Copyright (c) 2011-2015 Spotify AB 23 | * 24 | * Licensed under the Apache License, Version 2.0 (the "License"); 25 | * you may not use this file except in compliance with the License. 26 | * You may obtain a copy of the License at 27 | * 28 | * http://www.apache.org/licenses/LICENSE-2.0 29 | * 30 | * Unless required by applicable law or agreed to in writing, software 31 | * distributed under the License is distributed on an "AS IS" BASIS, 32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | * See the License for the specific language governing permissions and 34 | * limitations under the License. 35 | */ 36 | 37 | package com.spotify.google.cloud.pubsub.client.integration; 38 | 39 | import com.google.common.base.Throwables; 40 | import com.google.common.io.CharStreams; 41 | import java.io.IOException; 42 | import java.io.InputStreamReader; 43 | import java.security.NoSuchAlgorithmException; 44 | import java.util.stream.Stream; 45 | import javax.net.ssl.SSLContext; 46 | 47 | class Util { 48 | 49 | static final String TEST_NAME_PREFIX = "async-google-pubsub-client-test-"; 50 | 51 | private static String defaultProject = System.getenv("GOOGLE_CLOUD_PROJECT"); 52 | 53 | static String defaultProject() { 54 | 55 | if (defaultProject != null) { 56 | return defaultProject; 57 | } 58 | 59 | // Try asking gcloud 60 | try { 61 | Process process = Runtime.getRuntime().exec("gcloud config get-value project"); 62 | defaultProject = CharStreams.toString(new InputStreamReader(process.getInputStream())).trim(); 63 | } catch (IOException e) { 64 | throw new RuntimeException("failed to get default project", e); 65 | } 66 | 67 | if (defaultProject.isEmpty()) { 68 | throw new RuntimeException("got empty default project"); 69 | } 70 | 71 | return defaultProject; 72 | } 73 | 74 | /** 75 | * Return a list of non-GCM ciphers. GCM performance in Java 8 (pre 8u60) is unusably bad and currently worse than 76 | * CBC in >= 8u60. 77 | * 78 | * https://bugs.openjdk.java.net/browse/JDK-8069072 79 | */ 80 | static String[] nonGcmCiphers() { 81 | final SSLContext sslContext; 82 | try { 83 | sslContext = SSLContext.getDefault(); 84 | } catch (NoSuchAlgorithmException e) { 85 | throw Throwables.propagate(e); 86 | } 87 | 88 | final String[] defaultCiphers = sslContext.getDefaultSSLParameters().getCipherSuites(); 89 | 90 | return Stream.of(defaultCiphers) 91 | .filter(cipher -> !cipher.contains("GCM")) 92 | .toArray(String[]::new); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------