diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..103d7af9a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +* @kleewho @bartk @budgetpreneur +.travis/* @parfeon @kleewho @bartk @budgetpreneur +README.md @polarweasel @samiahmedsiddiqui @techwritermat diff --git a/.github/workflows/run_acceptance_tests.yml b/.github/workflows/run_acceptance_tests.yml new file mode 100644 index 000000000..15840c707 --- /dev/null +++ b/.github/workflows/run_acceptance_tests.yml @@ -0,0 +1,40 @@ +name: run_acceptance_tests + +on: [push] + +jobs: + build: + name: Perform Acceptance BDD tests + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v2 + - name: Checkout mock-server action + uses: actions/checkout@v2 + with: + repository: pubnub/client-engineering-deployment-tools + ref: github-actions + token: ${{ secrets.GH_TOKEN }} + path: client-engineering-deployment-tools + - name: Run mock server action + uses: ./client-engineering-deployment-tools/actions/mock-server + with: + token: ${{ secrets.GH_TOKEN }} + - name: Run acceptance tests + run: | + export pubKey=somePubKey + export subKey=someSubKey + export pamPubKey=somePamPubKey + export pamSubKey=somePamSubKey + export pamSecKey=someSecKey + export featuresDir=sdk-specifications/features/access + export cucumberTags=@feature=access + export serverHostPort=localhost:8090 + export serverMock=true + ./gradlew cucumber + - name: Expose acceptance tests reports + uses: actions/upload-artifact@v2 + with: + name: acceptance-test-reports + path: ./build/reports/cucumber-reports + diff --git a/.github/workflows/validate-pubnub-yml.yml b/.github/workflows/validate-pubnub-yml.yml new file mode 100644 index 000000000..5963a0ff8 --- /dev/null +++ b/.github/workflows/validate-pubnub-yml.yml @@ -0,0 +1,24 @@ +name: validate-pubnub-yml + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: [push] + +jobs: + build: + name: Validate PubNub yml + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Install dependencies + run: | + npm install ajv@6.12.6 + npm install yaml@1.10.0 + npm install node-fetch@2.6.1 + npm install chalk@2.4.2 + - name: Validate + run: GITHUB_TOKEN=${{ secrets.GH_TOKEN }} node ./.github/workflows/validate-yml.js diff --git a/.github/workflows/validate-yml.js b/.github/workflows/validate-yml.js new file mode 100644 index 000000000..b69ea465c --- /dev/null +++ b/.github/workflows/validate-yml.js @@ -0,0 +1,94 @@ +const YAML = require('yaml') +const Ajv = require('ajv'); +const fetch = require('node-fetch'); +const fs = require('fs'); +const chalk = require('chalk'); + +const ghToken = process.env.GITHUB_TOKEN; +const ghHeaders = {'User-Agent': 'sdk-bot', 'Authorization': 'token ' + ghToken,'Accept': 'application/vnd.github.v3.raw'}; + +const sdkReposJSONBranch = "develop"; +let sdkReposJSONPath = "http://api.github.com/repos/pubnub/documentation-resources/contents/website-common/tools/build/sdk-repos.json?ref=" + sdkReposJSONBranch; +startExecution(sdkReposJSONPath); + +async function startExecution(sdkReposJSONPath){ + var sdkRepos = await requestGetFromGithub(sdkReposJSONPath); + var sdkReposAndFeatureMappingArray = parseReposAndFeatureMapping(sdkRepos); + var schemaText = await requestGetFromGithub(sdkReposAndFeatureMappingArray[2]); + + schema = JSON.parse(schemaText); + var yaml = fs.readFileSync(".pubnub.yml", 'utf8'); + + if(yaml != null){ + yml = YAML.parse(yaml); + var ajv = new Ajv({schemaId: 'id', "verbose":true, "allErrors": true}); + const validate = ajv.compile(schema); + const valid = validate(yml); + if (validate.errors!= null) { + console.log(chalk.cyan("===================================")); + console.log(chalk.red(yml["version"] + " validation errors...")); + console.log(chalk.cyan("===================================")); + console.log(validate.errors); + console.log(chalk.cyan("===================================")); + var result = {code:1, repo: yml["version"], msg: "validation errors"}; + printResult(result); + process.exit(1); + } + else { + var result = {code: 0, repo: yml["version"], msg: "validation pass"}; + printResult(result); + } + } else { + var result = {code:1, repo: "yml null", msg: "validation errors"}; + printResult(result); + process.exit(1); + } +} + +function printResult(result){ + var str = result.repo + ", " + result.msg; + if(result.code === 0){ + console.log(chalk.green(str) + ", Code: " + result.code); + } else { + console.log(chalk.red(str) + ", Code: " + result.code); + } +} + +async function requestGetFromGithub(url){ + try { + const response = await fetch(url, { + headers: ghHeaders, + method: 'get', + }); + if(response.status == 200){ + const json = await response.text(); + return json; + } else { + console.error(chalk.red("res.status: " + response.status + "\n URL: " + url)); + return null; + } + + } catch (error) { + console.error(chalk.red("requestGetFromGithub: " + error + "\n URL: " + url)); + return null; + } +} + +function parseReposAndFeatureMapping(body){ + if(body != null){ + var sdkRepos = JSON.parse(body); + var locations = sdkRepos["locations"]; + if(locations!=null){ + var sdkURLs = locations["sdks"]; + var featureMappingURL = locations["featureMapping"]; + var pubnubYAMLSchemaURL = locations["pubnubYAMLSchema"]; + return [sdkURLs, featureMappingURL, pubnubYAMLSchemaURL]; + } else { + console.log(chalk.red("response locations null")); + return null; + } + } else { + console.log(chalk.red("response body null")); + return null; + } +} diff --git a/android/.gitignore b/.gitignore similarity index 52% rename from android/.gitignore rename to .gitignore index 648a292a7..d5849dc14 100644 --- a/android/.gitignore +++ b/.gitignore @@ -27,3 +27,27 @@ proguard/ *.ipr *.iws .idea/ + +*.class + +# gwt caches and compiled units # +war/gwt_bree/ +gwt-unitCache/ + +# boilerplate generated classes # +.apt_generated/ + +# more caches and things from deploy # +war/WEB-INF/deploy/ +war/WEB-INF/classes/ + +# docs +docs + +target +build +.gradle +out +gradlew.bat + +/src/integrationTest/resources/config.properties diff --git a/.pubnub.yml b/.pubnub.yml new file mode 100644 index 000000000..667339614 --- /dev/null +++ b/.pubnub.yml @@ -0,0 +1,888 @@ +name: java +version: 5.2.1 +schema: 1 +scm: github.com/pubnub/java +files: + - build/libs/pubnub-gson-5.2.1-all.jar +sdks: + - + type: library + full-name: Java SDK + short-name: Java + artifacts: + - + language: java + tags: + - Server + - Mobile + source-repository: https://github.com/pubnub/java + documentation: https://www.pubnub.com/docs/sdks/java/ + tier: 1 + artifact-type: library + distributions: + - + distribution-type: library + distribution-repository: git release + package-name: pubnub-gson-4.33.3-all + location: https://github.com/pubnub/java/releases/download/v4.33.3/pubnub-gson-4.33.3-all.jar + supported-platforms: + supported-operating-systems: + Android: + runtime-version: + - ART + target-api-level: + - 23 + minimum-api-level: + - 23 + maximum-api-level: + - 30 + target-architecture: + - armeabi-v7a + - atom + - armeabi + - arm64-v8a + Linux: + runtime-version: + - JVM 8 + minimum-os-version: + - Ubuntu 12.04 + maximum-os-version: + - Ubuntu 20.04 LTS + target-architecture: + - x86 + - x86-64 + macOS: + runtime-version: + - JVM 8 + minimum-os-version: + - macOS 10.12 + maximum-os-version: + - macOS 11.0.1 + target-architecture: + - x86-64 + Windows: + runtime-version: + - JVM 8 + minimum-os-version: + - Windows Vista Ultimate + maximum-os-version: + - Windows 10 Home + target-architecture: + - x86 + - x86-64 + requires: + - + name: retrofit + min-version: 2.6.2 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: https://github.com/square/retrofit/blob/parent-2.6.2/LICENSE.txt + is-required: Required + - + name: okhttp + min-version: 3.12.6 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: https://github.com/square/okhttp/blob/parent-3.12.6/LICENSE.txt + is-required: Required + - + name: converter-gson + min-version: 2.6.2 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: https://github.com/square/retrofit/blob/parent-2.6.2/LICENSE.txt + is-required: Required + - + name: gson + min-version: 2.8.6 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: https://github.com/google/gson/blob/gson-parent-2.8.6/LICENSE + is-required: Required + - + name: jackson-databind + min-version: 2.9.9 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: hhttps://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.9.9/README.md + is-required: Required + - + name: jackson-module-kotlin + min-version: 2.9.9 + location: Shipped within library + license: Apache License, Version 2.0 + license-url: "" + is-required: Required + - + name: json + min-version: "20190722" + location: Shipped within library + license: "" + license-url: https://github.com/stleary/JSON-java/blob/20190722/LICENSE + is-required: Required + + - + + language: java + tags: + - Server + - Mobile + source-repository: https://github.com/pubnub/java + documentation: https://www.pubnub.com/docs/sdks/java + tier: 1 + artifact-type: library + distributions: + - + distribution-type: library + distribution-repository: maven + package-name: pubnub-gson-4.33.3 + location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-gson/4.33.3/pubnub-gson-4.33.3.jar + supported-platforms: + supported-operating-systems: + Android: + runtime-version: + - ART + target-api-level: + - 23 + minimum-api-level: + - 23 + maximum-api-level: + - 30 + target-architecture: + - armeabi-v7a + - atom + - armeabi + - arm64-v8a + Linux: + runtime-version: + - JVM 8 + minimum-os-version: + - Ubuntu 12.04 + maximum-os-version: + - Ubuntu 20.04 LTS + target-architecture: + - x86 + - x86-64 + macOS: + runtime-version: + - JVM 8 + minimum-os-version: + - macOS 10.12 + maximum-os-version: + - macOS 11.0.1 + target-architecture: + - x86-64 + Windows: + runtime-version: + - JVM 8 + minimum-os-version: + - Windows Vista Ultimate + maximum-os-version: + - Windows 10 Home + target-architecture: + - x86 + - x86-64 + requires: + - + name: retrofit + min-version: 2.6.2 + location: https://repo.maven.apache.org/maven2/com/squareup/retrofit2/retrofit/2.6.2/retrofit-2.6.2.jar + license: Apache License, Version 2.0 + license-url: https://github.com/square/retrofit/blob/parent-2.6.2/LICENSE.txt + is-required: Required + - + name: okhttp + min-version: 3.12.6 + location: https://repo.maven.apache.org/maven2/com/squareup/okhttp3/okhttp/3.12.6/okhttp-3.12.6.jar + license: Apache License, Version 2.0 + license-url: https://github.com/square/okhttp/blob/parent-3.12.6/LICENSE.txt + is-required: Required + - + name: converter-gson + min-version: 2.6.2 + location: https://repo.maven.apache.org/maven2/com/squareup/retrofit2/converter-gson/2.6.2/converter-gson-2.6.2.jar + license: Apache License, Version 2.0 + license-url: https://github.com/square/retrofit/blob/parent-2.6.2/LICENSE.txt + is-required: Required + - + name: gson + min-version: 2.8.6 + location: https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar + license: Apache License, Version 2.0 + license-url: https://github.com/google/gson/blob/gson-parent-2.8.6/LICENSE + is-required: Required + - + name: jackson-databind + min-version: 2.9.9 + location: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.9.9/jackson-databind-2.9.9.jar + license: Apache License, Version 2.0 + license-url: hhttps://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.9.9/README.md + is-required: Required + - + name: jackson-module-kotlin + min-version: 2.9.9 + location: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/module/jackson-module-kotlin/2.9.9/jackson-module-kotlin-2.9.9.jar + license: Apache License, Version 2.0 + license-url: "" + is-required: Required + - + name: json + min-version: "20190722" + location: https://repo.maven.apache.org/maven2/org/json/json/20190722/json-20190722.jar + license: "" + license-url: https://github.com/stleary/JSON-java/blob/20190722/LICENSE + is-required: Required + +changelog: + - version: v5.2.1 + date: 2021-10-06 + changes: + - type: feature + text: "Acceptance tests plugged into CI pipeline." + - type: bug + text: "Meta field exposed correctly in PNToken class." + - version: v5.2.0 + date: 2021-09-08 + changes: + - type: feature + text: "Extend grantToken method to enable control of Objects API permission. Enhance granularity of permission control to enable permissions per UUID." + - version: v5.1.1 + date: 2021-07-13 + changes: + - type: bug + text: "Update Jackson libraries to avoid known vulnerabilities." + - version: v5.1.0 + date: 2021-05-20 + changes: + - type: feature + text: "Method grantToken has beed added. It allows generation of signed token with permissions for channels and channel groups." + - type: bug + text: "UUID is now exposed as PNMembership field which make is accessible from PNMembershipResult argument of SubscribeCallback.membership() method." + - version: v5.0.0 + date: 2021-05-12 + changes: + - type: feature + text: "Now random initialisation vector used when encryption enabled is now default behaviour." + - type: bug + text: "There were some non daemon threads running in background preventing VM from exiting. Now they are daemon threads." + - version: v4.36.0 + date: 2021-04-08 + changes: + - type: feature + text: "New way of controlling Presence by Heartbeat calls for purpose of usage with dedicated server configuration (ACL). This feature can be used only with additional support from PubNub." + - version: v4.33.3 + date: 2020-10-21 + changes: + - type: bug + text: "Improved handling of random initialization vector for encrypting messages." + - type: bug + text: "Restore Android compatibility for Gradle 3.X by removing Stringjoin()." + - type: bug + text: "Return appropriate error information when payload is too large." + - version: v4.33.2 + date: 2020-10-08 + changes: + - type: bug + text: "To improve security of messages, added support for random initialization vector to encrypt and decrypt messages." + - version: v4.33.1 + date: 2020-09-24 + changes: + - type: bug + text: "PubNubException now overrides Throwable's `getMessage` so the `status.errorData.throwablemessage` can be properly set." + - version: v4.33.0 + date: 2020-09-14 + changes: + - type: feature + text: "Objects (v2) API exposed to enable metadata management." + - type: feature + text: "Enable Objects (v2) related permissions management via Grant method." + - version: v4.32.1 + date: 2020-08-24 + changes: + - type: bug + text: "Fix for subscription loop to prevent unexpected disconnections caused by unhandled HTTP statuses." + - version: v4.32.0 + date: 2020-08-14 + changes: + - type: feature + text: "Allows to upload files to channels, download them with optional encryption support." + - version: v4.31.3 + date: 2020-06-17 + changes: + - type: bug + text: "Fix typo in suppressLeaveEvents in PNConfiguration." + - version: v4.31.2 + date: 2020-06-12 + changes: + - type: feature + text: "Add \"click_action\" parameter to PushPayloadHelper in order to pass it to FCM." + - version: v4.31.1 + date: 2020-04-16 + changes: + - type: bug + text: Fix OkHttp reconnection policy + - version: v4.31.0 + date: 2020-02-25 + changes: + - type: feature + text: Implemented Objects Filtering API + - type: improvement + text: Handled more network events to keep the client subscribed + - type: improvement + text: Improved interaction with classes from org.json* + - type: improvement + text: Made PNCallback eligible for SAM/lambda conversion + - type: improvement + text: Deprecated PNPushType.GCM in favor of PNPushType.FCM + - version: v4.30.0 + date: 2020-01-23 + changes: + - type: feature + text: Add support for APNS2 Push API + - type: feature + text: Add a utility class to ease creating push payloads + - version: v4.29.2 + date: 2019-12-02 + changes: + - type: improvement + text: Disable OkHttp reconnection policy + - version: v4.29.1 + date: 2019-10-22 + changes: + - type: improvement + text: Made the SDK more Kotlin-friendly + - type: improvement + text: Categorized cancelled requests as such + - type: improvement + text: Removed the 'audit' method + - version: v4.29.0 + date: 2019-10-08 + changes: + - type: feature + text: Implemented Message Actions API + - type: feature + text: Added 'includeMeta' to history() + - type: feature + text: Added 'includeMeta' to fetchMessages() + - type: feature + text: Added 'includeMessageActions' to fetchMessages() + - version: v4.28.0 + date: 2019-10-01 + changes: + - type: feature + text: Add PAMv3 support + - type: feature + text: Implement TMS (Token Manager) + - type: feature + text: Upgrade PAM endpoints to v2 + - type: feature + text: Introduce delete permission for grant + - version: v4.27.0 + date: 2019-08-27 + changes: + - type: feature + text: Add Objects API support + - version: v4.26.1 + date: 2019-08-13 + changes: + - type: feature + text: Introduce serialization class for Signals API + - version: v4.26.0 + date: 2019-08-09 + changes: + - type: feature + text: Add Signals support + - type: bug + text: Expose OkHttp logging interceptor library + - version: v4.25.0 + date: 2019-06-06 + changes: + - type: bug + text: Enable app level grants + - type: bug + text: Custom encode auth key where it's not encoded automatically + - type: feature + text: Move state param from heartbeat to subscribe calls + - version: v4.24.0 + date: 2019-05-21 + changes: + - type: feature + text: Enforce a minimum presence timeout value + - type: feature + text: Disable presence heartbeats by default + - version: v4.23.0 + date: 2019-05-08 + changes: + - type: bug + text: Fix reconnection issues by allowing it solely for network issues + - version: v4.22.0 + date: 2019-04-26 + changes: + - type: feature + text: Introduce Message Count API + - type: feature + text: Update 3rd party libraries + - version: v4.22.0-beta + date: 2019-01-31 + changes: + - type: feature + text: Update 3rd party libraries + - version: v4.21.0 + date: 2018-10-26 + changes: + - type: feature + text: Support optional query parameters for every request + - type: feature + text: Update documentation + - version: v4.20.0 + date: 2018-08-07 + changes: + - type: bug + text: Fix an issue where the global-here-now response was interpreted incorrectly + - version: v4.19.0 + date: 2018-03-29 + changes: + - type: bug + text: Fix an issue where end of channel history was interpreted as an error + - version: v4.18.0 + date: 2018-01-11 + changes: + - type: improvement + text: lock down OkHttp version to support latest android version + - version: v4.17.0 + date: 2017-12-19 + changes: + - type: improvement + text: allow SDK to only send heartbeats without subscribing to the data channel. + - version: v4.16.0 + date: 2017-11-21 + changes: + - type: improvement + text: allow setting setMaximumConnections to open more connections to PubNub + - version: v4.15.0 + date: 2017-11-17 + changes: + - type: improvement + text: update gson dependency + - type: bug + text: make listeners thread safe + - type: bug + text: close hanging threads on shutdown + - version: v4.14.0 + date: 2017-10-25 + changes: + - type: improvement + text: add support to suppress leave events + - version: v4.13.0 + date: 2017-10-23 + changes: + - type: improvement + text: do not execute subscribe on empty string channel, channel groups + - type: improvement + text: stop heartbeat loop if an error shows up. + - version: v4.12.0 + date: 2017-10-05 + changes: + - type: bug + text: fix worker thread unloading. + - type: feature + text: prevent concurrent modification of listeners. + - version: v4.11.0 + date: 2017-10-05 + changes: + - type: bug + text: fix retrofit unloading. + - version: v4.10.0 + date: 2017-09-17 + changes: + - type: feature + text: rework the loading of services to load the classes once. + - version: v4.9.1 + date: 2017-08-14 + changes: + - type: feature + text: patch-up to the deduping algorithm + - version: v4.9.0 + date: 2017-08-14 + changes: + - type: feature + text: Internal deduping mechanism when devices cross regions (dedupOnSubscribe). + - version: v4.8.0 + date: 2017-08-08 + changes: + - type: feature + text: Allow certificate pinning via setCertificatePinner on PNConfiguration + - type: feature + text: Allow disabling of heartbeat by setting the interval to 0. + - type: feature + text: GAE fixes. + - version: v4.7.0 + date: 2017-07-20 + changes: + - type: feature + text: Allow injection of httpLoggingInterceptor for extra logging monitoring.. + - version: v4.6.5 + date: 2017-06-28 + changes: + - type: bug + text: adjust queue exceeded notifications to be greater or equal of. + - version: v4.6.4 + date: 2017-06-10 + changes: + - type: bug + text: gracefully handle disabled history + - version: v4.6.3 + date: 2017-06-03 + changes: + - type: feature + text: on interval events, pass hereNowRefresh to indicate if a here_now fetch is needed. + - version: v4.6.2 + date: 2017-04-13 + changes: + - type: feature + text: set a name for Subscription Manager Consumer Thead. + - version: v4.6.1 + date: 2017-04-06 + changes: + - type: bug + text: SDK crash in Android with Airplane Mode + - type: feature + text: add deltas on interval action. + - version: v4.6.0 + date: 2017-03-14 + changes: + - type: feature + text: To distinguish UUID's that were generated by our SDK, we appended `pn-` before the UUID to signal that it's a randomly generated UUID. + - type: feature + text: Allow the passing of OkHttp connection spec via setConnectionSpec + - type: improvement + text: Bump retrofit to 2.2.0 + - version: v4.5.0 + date: 2017-02-15 + changes: + - type: feature + text: add .toString methods to all public facing models and POJOs + - version: v4.4.4 + date: 2017-02-06 + changes: + - type: feature + text: Add support to configure host name verifier. + - version: v4.4.3 + date: 2017-02-02 + changes: + - type: feature + text: Add support to configure custom certificate pinning via SSLSocketFactory and X509 configuration objects. + - version: v4.4.2 + date: 2017-01-31 + changes: + - type: bug + text: SDK was not sending the user metadata on Message Callback + - version: v4.4.1 + date: 2017-01-25 + changes: + - type: bug + text: SDK did not honor the exhaustion of reconnections, it will now disconnect once max retries happened + - version: v4.4.0 + date: 2017-01-24 + changes: + - type: improvement + text: Support for maximum reconnection attempts + - type: improvement + text: Populate affectedChannel and affectedChannelGroups + - type: improvement + text: Support for GAE + - type: improvement + text: Emit pnconnected when adding / removing channels. + - version: v4.3.1 + date: 2016-12-22 + changes: + - type: improvement + text: support for key-level grant. + - version: v4.3.0 + date: 2016-12-14 + changes: + - type: improvement + text: JSON parser is switched to GSON, new artifact on nexus as pubnub-gson + - type: improvement + text: GetState, setState return a JsonElement instead of a plain object. + - version: v4.2.3 + date: + changes: + - type: improvement + text: Swapping out logger for SLF4J API and removing final methods + - version: v4.2.2 + date: 2016-12-09 + changes: + - type: improvement + text: remove final identifiers from the public facing API. + - version: v4.2.1 + date: 2016-11-23 + changes: + - type: improvement + text: include publisher UUID on incoming message + - type: improvement + text: allow to set custom TTL on a publish + - version: v4.2.0 + date: 2016-10-25 + changes: + - type: improvement + text: Signatures are generated for all requests with secret key to ensure secure transmission of data + - type: improvement + text: support for alerting of queue exceeded (PNRequestMessageCountExceededCategory) + - type: improvement + text: signaling to OkHttp to stop the queues on termination. + - version: v4.1.0 + date: 2016-10-12 + changes: + - type: improvement + text: destroy now correctly forces the producer thread to shut down; stop is now deprecated for disconnect + - type: improvement + text: support for sending instance id for presence detection (disabled by default) + - type: improvement + text: support for sending request id to burst cache (enabled by default) + - type: improvement + text: proxy support via the native proxy configurator class. + - version: v4.0.14 + date: 2016-09-20 + changes: + - type: improvement + text: on PAM error, populate the affectedChannel or affectedChannelGroup to signal which channels are failing + - version: v4.0.13 + date: 2016-09-14 + changes: + - type: improvement + text: populate jso with the error. + - version: v4.0.12 + date: 2016-09-13 + changes: + - type: bug + text: fixing parsing of origination payload within the psv2 envelope + - version: v4.0.11 + date: 2016-09-09 + changes: + - type: improvement + text: bumping build process for gradle 3 / merging documentation into the repo and test adjustments + - version: v4.0.10 + date: 2016-09-07 + changes: + - type: improvement + text: adding channel / channelGroup fields when a message / presence event comes in. + - version: v4.0.9 + date: 2016-08-24 + changes: + - type: improvement + text: adjustments for handling pn_other and decryption + - type: improvement + text: retrofit version bumps. + - version: v4.0.8 + date: 2016-08-16 + changes: + - type: feature + text: added unsubscribeAll, getSubscribedChannels, getSubscribedChannelGroups + - type: feature + text: SDK will establish secure connections by default + - type: feature + text: added support for exponential backoff reconnection policies + - version: v4.0.7 + date: 2016-08-11 + changes: + - type: improvement + text: reduce overlap on error handling when returning exceptions. + - version: v4.0.6 + date: 2016-07-18 + changes: + - type: improvement + text: send heartbeat presence value when subscribing + - version: v4.0.5 + date: 2016-07-07 + changes: + - type: improvement + text: unified retrofit handling to lower amount of instances and sync'd the state methods. + - version: v4.0.4 + date: 2016-06-24 + changes: + - type: bug + text: setting State for other UUID's is now supported. + - version: v4.0.3 + date: 2016-06-15 + changes: + - type: feature + text: fire() method and no-replication options. + - version: v4.0.2 + date: 2016-06-15 + changes: + - type: bug + text: fix to the version fetching. + - version: v4.0.1 + date: 2016-06-06 + changes: + - type: bug + text: adjustment of the subscribe loop to alleviate duplicate dispatches. + - version: v4.0.0 + date: 2016-06-03 + changes: + - type: bug + text: first GA. + - version: v4.0.0-beta4 + date: 2016-06-03 + changes: + - type: improvement + text: reconnects and minor adjustments. + - version: v4.0.0-beta3 + date: 2016-06-03 + changes: + - type: bug + text: fixing state not coming on the subscriber callback. + - type: bug + text: adjustments to URL encoding on publish, subscribe, set-state operations to avoid double encoding with retrofit. + - version: v4.0.0-beta2 + date: 2016-06-03 + changes: + - type: improvement + text: reworking of message queue. + - type: improvement + text: checkstyle, findbugs. + - type: improvement + text: reworking error notifications. + - version: v4.0.0-beta1 + date: 2016-06-03 + changes: + - type: improvement + text: initial beta1. +features: + access: + - ACCESS-GRANT + - ACCESS-GRANT-MANAGE + - ACCESS-GRANT-DELETE + - ACCESS-SECRET-KEY-ALL-ACCESS + - ACCESS-GRANT-TOKEN + - ACCESS-PARSE-TOKEN + - ACCESS-SET-TOKEN + channel-groups: + - CHANNEL-GROUPS-ADD-CHANNELS + - CHANNEL-GROUPS-REMOVE-CHANNELS + - CHANNEL-GROUPS-REMOVE-GROUPS + - CHANNEL-GROUPS-LIST-CHANNELS-IN-GROUP + notify: + - REQUEST-MESSAGE-COUNT-EXCEEDED + push: + - PUSH-ADD-DEVICE-TO-CHANNELS + - PUSH-REMOVE-DEVICE-FROM-CHANNELS + - PUSH-LIST-CHANNELS-FROM-DEVICE + - PUSH-REMOVE-DEVICE + - PUSH-TYPE-APNS + - PUSH-TYPE-APNS2 + - PUSH-TYPE-FCM + - PUSH-TYPE-MPNS + presence: + - PRESENCE-HERE-NOW + - PRESENCE-WHERE-NOW + - PRESENCE-SET-STATE + - PRESENCE-GET-STATE + - PRESENCE-HEARTBEAT + publish: + - PUBLISH-STORE-FLAG + - PUBLISH-RAW-JSON + - PUBLISH-WITH-METADATA + - PUBLISH-GET + - PUBLISH-POST + - PUBLISH-ASYNC + - PUBLISH-FIRE + - PUBLISH-REPLICATION-FLAG + - PUBLISH-MESSAGE-TTL + storage: + - STORAGE-REVERSE + - STORAGE-INCLUDE-TIMETOKEN + - STORAGE-START-END + - STORAGE-COUNT + - STORAGE-FETCH-MESSAGES + - STORAGE-DELETE-MESSAGES + - STORAGE-MESSAGE-COUNT + - STORAGE-HISTORY-WITH-META + - STORAGE-FETCH-WITH-META + - STORAGE-FETCH-WITH-MESSAGE-ACTIONS + time: + - TIME-TIME + subscribe: + - SUBSCRIBE-CHANNELS + - SUBSCRIBE-CHANNEL-GROUPS + - SUBSCRIBE-PRESENCE-CHANNELS + - SUBSCRIBE-PRESENCE-CHANNELS-GROUPS + - SUBSCRIBE-WITH-TIMETOKEN + - SUBSCRIBE-WILDCARD + - SUBSCRIBE-FILTER-EXPRESSION + - SUBSCRIBE-PUBLISHER-UUID + - SUBSCRIBE-PUBSUB-V2 + - SUBSCRIBE-SIGNAL-LISTENER + - SUBSCRIBE-MEMBERSHIP-LISTENER + - SUBSCRIBE-SPACE-LISTENER + - SUBSCRIBE-USER-LISTENER + - SUBSCRIBE-MESSAGE-ACTIONS-LISTENER + signal: + - SIGNAL-SEND + objects: + - OBJECTS-GET-USERS + - OBJECTS-GET-USER + - OBJECTS-CREATE-USER + - OBJECTS-UPDATE-USER + - OBJECTS-DELETE-USER + - OBJECTS-GET-SPACES + - OBJECTS-CREATE-SPACE + - OBJECTS-GET-SPACE + - OBJECTS-UPDATE-SPACE + - OBJECTS-DELETE-SPACE + - OBJECTS-GET-MEMBERSHIPS + - OBJECTS-MANAGE-MEMBERSHIPS + - OBJECTS-GET-MEMBERS + - OBJECTS-MANAGE-MEMBERS + - OBJECTS-JOIN-SPACES + - OBJECTS-UPDATE-MEMBERSHIPS + - OBJECTS-LEAVE-SPACES + - OBJECTS-ADD-MEMBERS + - OBJECTS-REMOVE-MEMBERS + - OBJECTS-UPDATE-MEMBERS + - OBJECTS-FILTERING + files: + - FILES-DELETE-FILE + - FILES-DOWNLOAD-FILE + - FILES-GET-FILE-URL + - FILES-LIST-FILES + - FILES-SEND-FILE + unsubscribe: + - UNSUBSCRIBE-ALL + - UNSUBSCRIBE-SUPPRESS-LEAVE-EVENTS + message-actions: + - MESSAGE-ACTIONS-GET + - MESSAGE-ACTIONS-ADD + - MESSAGE-ACTIONS-REMOVE + others: + - TELEMETRY + - QUERY-PARAM + - PN-OTHER-PROCESSING + - CREATE-PUSH-PAYLOAD +supported-platforms: + - version: PubNub Java SDK + platforms: + - Windows 10 (8u51 and above) + - Windows 8.x (Desktop) + - Windows 7 SP1 + - Windows Vista SP2 + - Windows Server 2008 R2 SP1 (64-bit) + - Windows Server 2012 and 2012 R2 (64-bit) + - Intel-based Mac running Mac OS X 10.8.3+, 10.9+ + - Oracle Linux 5.5+ + - Oracle Linux 6.x + - Oracle Linux 7.x (64-bit) (8u20 and above) + - Red Hat Enterprise Linux 5.5+, 6.x + - Red Hat Enterprise Linux 7.x (64-bit) (8u20 and above) + - Suse Linux Enterprise Server 10 SP2+, 11.x + - Suse Linux Enterprise Server 12.x (64-bit) (8u31 and above) + - Ubuntu Linux 12.04 LTS, 13.x + - Ubuntu Linux 14.x (8u25 and above) + - Ubuntu Linux 15.04 (8u45 and above) + - Ubuntu Linux 15.10 (8u65 and above) + editors: + - Java8+ + - version: PubNub Android SDK + platforms: + - Android 2.3.1+ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..a537388e9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +jdk: oraclejdk8 +language: java +dist: trusty +os: linux + + +before_install: + - wget https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/2.0.0/codacy-coverage-reporter-2.0.0-assembly.jar + +install: skip + + +stages: + - name: "test" + +jobs: + include: + - stage: "test" + name: "Build & test" + script: + - ./gradlew assemble + - ./gradlew check + after_success: + - java -cp ~/codacy-coverage-reporter-2.0.0-assembly.jar com.codacy.CodacyCoverageReporter -l Java -r build/reports/jacoco/test/jacocoTestReport.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..43cb7cc34 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,903 @@ +## [v5.2.1](https://github.com/pubnub/java/releases/tag/v5.2.1) +October-06-2021 + +[Full Changelog](https://github.com/pubnub/java/compare/v5.2.0...v5.2.1) + +- 🌟️ Acceptance tests plugged into CI pipeline. +- 🐛 Meta field exposed correctly in PNToken class. + +## [v5.2.0](https://github.com/pubnub/java/releases/tag/v5.2.0) +September-08-2021 + +- 🌟️ Extend grantToken method to enable control of Objects API permission. Enhance granularity of permission control to enable permissions per UUID. + +## [v5.1.1](https://github.com/pubnub/java/releases/tag/v5.1.1) +July-13-2021 + +- 🐛 Update Jackson libraries to avoid known vulnerabilities. + +## [v5.1.0](https://github.com/pubnub/java/releases/tag/v5.1.0) +May-20-2021 + +- 🌟️ Method grantToken has beed added. It allows generation of signed token with permissions for channels and channel groups. +- 🐛 UUID is now exposed as PNMembership field which make is accessible from PNMembershipResult argument of SubscribeCallback.membership() method. + +## [v5.0.0](https://github.com/pubnub/java/releases/tag/v5.0.0) +May-12-2021 + +- 🌟️ Now random initialisation vector used when encryption enabled is now default behaviour. +- 🐛 There were some non daemon threads running in background preventing VM from exiting. Now they are daemon threads. + +## [v4.36.0](https://github.com/pubnub/java/releases/tag/v4.36.0) +April-08-2021 + +- 🌟️ New way of controlling Presence by Heartbeat calls for purpose of usage with dedicated server configuration (ACL). This feature can be used only with additional support from PubNub. + +## [v4.33.3](https://github.com/pubnub/java/releases/tag/v4.33.3) +October-21-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.33.2...v4.33.3) + +- 🐛 Improved handling of random initialization vector for encrypting messages. +- 🐛 Restore Android compatibility for Gradle 3.X by removing Stringjoin(). +- 🐛 Return appropriate error information when payload is too large. + +## [v4.33.2](https://github.com/pubnub/java/releases/tag/v4.33.2) +October-08-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.33.1...v4.33.2) + +- 🐛 To improve security of messages, added support for random initialization vector to encrypt and decrypt messages. + +## [v4.33.1](https://github.com/pubnub/java/releases/tag/v4.33.1) +September-24-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.33.0...v4.33.1) + +- 🐛 PubNubException now overrides Throwable's `getMessage` so the `status.errorData.throwablemessage` can be properly set. + +## [v4.33.0](https://github.com/pubnub/java/releases/tag/v4.33.0) +September-14-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.32.1...v4.33.0) + +- 🌟️ Objects (v2) API exposed to enable metadata management. +- 🌟️ Enable Objects (v2) related permissions management via Grant method. + +## [v4.32.1](https://github.com/pubnub/java/releases/tag/v4.32.1) +August-24-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.32.0...v4.32.1) + +- 🐛 Fix for subscription loop to prevent unexpected disconnections caused by unhandled HTTP statuses. + +## [v4.32.0](https://github.com/pubnub/java/releases/tag/v4.32.0) +August-14-2020 + +- 🌟️ Allows to upload files to channels, download them with optional encryption support. + +## [v4.31.3](https://github.com/pubnub/java/releases/tag/v4.31.3) +June-17-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.31.2...v4.31.3) + +- 🐛 Fix typo in suppressLeaveEvents in PNConfiguration. + +## [v4.31.2](https://github.com/pubnub/java/releases/tag/v4.31.2) +June-12-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.31.1...v4.31.2) + +- 🌟 Add "click_action" parameter to PushPayloadHelper in order to pass it to FCM. + +## [v4.31.1](https://github.com/pubnub/java/releases/tag/v4.31.1) +April-16-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.31.0...v4.31.1) + +- 🐛 Fix OkHttp reconnection policy. + +## [v4.31.0](https://github.com/pubnub/java/tree/v4.31.0) +February-25-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.30.0...v4.31.0) + +- 🌟️ Implemented Objects Filtering API +- ⭐ Handled more network events to keep the client subscribed. +- ⭐ Improved interaction with classes from org.json*. +- ⭐ Made PNCallback eligible for SAM/lambda conversion. +- ⭐ Deprecated PNPushType.GCM in favor of PNPushType.FCM. + +## [v4.30.0](https://github.com/pubnub/java/tree/v4.30.0) +January-23-2020 + +[Full Changelog](https://github.com/pubnub/java/compare/v4.29.2...v4.30.0) + +- 🌟️ Add support for APNS2 Push API. +- 🌟️ Add a utility class to ease creating push payloads. + +## [v4.29.2](https://github.com/pubnub/java/tree/v4.29.2) + December-02-2019 + + [Full Changelog](https://github.com/pubnub/java/compare/v4.29.1...v4.29.2) + +- ⭐ Disable Okhttp retry on failure + + +## [v4.29.1](https://github.com/pubnub/java/tree/v4.29.1) + October-23-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.29.0...v4.29.1) + + +- ⭐Made the SDK more Kotlin-friendly +- ⭐Categorized canceled requests as such +- ⭐Removed the ‘audit’ method + + +## [v4.29.0](https://github.com/pubnub/java/tree/v4.29.0) + October-09-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.28.0...v4.29.0) + + +- ⭐Implemented the Message Actions API +- ⭐Added includeMeta() to history() +- ⭐Added includeMeta() to fetchMessages() +- ⭐Added includeMessageActions() to fetchMessages() + + +## [v4.28.0](https://github.com/pubnub/java/tree/v4.28.0) + October-02-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.27.0...v4.28.0) + + +- ⭐Added PAMv3 support +- ⭐Added Token manager (TMS) +- ⭐Upgraded grant() and audit() to /v2/ endpoints +- ⭐Implemented the delete permission for grant() requests +- ⭐Implemented the v2 signature to be used for signing most requests + + +## [v4.27.0](https://github.com/pubnub/java/tree/v4.27.0) + August-27-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.26.1...v4.27.0) + + +- ⭐Added Objects API support + + +## [v4.26.1](https://github.com/pubnub/java/tree/v4.26.1) + August-14-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.26.0...v4.26.1) + + +- ⭐Introduced serialization class for Signals API + + +## [v4.26.0](https://github.com/pubnub/java/tree/v4.26.0) + August-10-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.25.0...v4.26.0) + + +- ⭐Implemented Signals API +- ⭐Exposed OkHttp logging interceptor library + + +## [v4.25.0](https://github.com/pubnub/java/tree/v4.25.0) + June-10-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.24.0...v4.25.0) + + +- ⭐Enabled app level grants +- ⭐Implemented custom encoding of the auth key for APIs where it wasn’t encoded automatically +- ⭐Attached state data to Subscribe API and removed it from heartbeats + + +## [v4.24.0](https://github.com/pubnub/java/tree/v4.24.0) + May-22-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.23.0...v4.24.0) + + +- ⭐Enforced a minimum presence timeout value +- ⭐Disabled presence heartbeats by default +- ⭐Exposed Gson dependency + + +## [v4.23.0](https://github.com/pubnub/java/tree/v4.23.0) + May-08-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.22.0...v4.23.0) + + +- ⭐Fixed reconnection logic by allowing it solely for network issues + + +## [v4.22.0](https://github.com/pubnub/java/tree/v4.22.0) + April-26-2019 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.21.0...v4.22.0) + + +- ⭐Implemented Message Counts API +- ⭐Performed a major update of 3rd party libraries (e.g. Retrofit, OkHttp, Gson) +- ⭐Refactored and updated unit tests +- ⭐Replaced compile with implementation for 3rd party libraries. + + +## [v4.21.0](https://github.com/pubnub/java/tree/v4.21.0) + October-30-2018 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.20.0...v4.21.0) + + +- ⭐Implemented a feature where you can add optional query params to every request +- ⭐Updated developer setup documentation +- ⭐Improved code checkstyle rules + + +## [v4.20.0](https://github.com/pubnub/java/tree/v4.20.0) + September-04-2018 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.19.0...v4.20.0) + + +- ⭐Fix a bug where the global-here-now response was incorrectly interpreted + + +## [v4.19.0](https://github.com/pubnub/java/tree/v4.20.0) + April-04-2018 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.18.0...v4.19.0) + + +- ⭐Fix an issue where end of channel history was interpreted as an error + + + +## [v4.18.0](https://github.com/pubnub/java/tree/v4.18.0) + January-11-2018 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.17.0...v4.18.0) + + +- ⭐lock down okHTTP version to support latest android version + + + +## [v4.17.0](https://github.com/pubnub/java/tree/v4.17.0) + December-19-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.16.0...v4.17.0) + + +- ⭐allow SDK to only send heartbeats without subscribing to the data channel. + + + +## [v4.16.0](https://github.com/pubnub/java/tree/v4.16.0) + November-21-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.15.0...v4.16.0) + + +- ⭐allow setting setMaximumConnections to open more connections to PubNub + + + +## [v4.15.0](https://github.com/pubnub/java/tree/v4.15.0) + November-17-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.14.0...v4.15.0) + + +- ⭐update gson dependency + + + +- 🐛make listeners thread safe + + +- 🐛close hanging threads on shutdown + + +## [v4.14.0](https://github.com/pubnub/java/tree/v4.14.0) + October-25-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.13.0...v4.14.0) + + +- ⭐add support to supress leave events + + + +## [v4.13.0](https://github.com/pubnub/java/tree/v4.13.0) + October-23-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.12.0...v4.13.0) + + +- ⭐do not execute subscribe on empty string channel, channel groups + + +- ⭐stop heartbeat loop if an error shows up. + + + +## [v4.12.0](https://github.com/pubnub/java/tree/v4.12.0) + October-05-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.11.0...v4.12.0) + + + +- 🐛fix worker thread unloading. +- 🌟prevent concurrent modification of listeners. + + + + +## [v4.11.0](https://github.com/pubnub/java/tree/v4.11.0) + October-05-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.10.0...v4.11.0) + + + +- 🐛fix retrofit unloading. + + +## [v4.10.0](https://github.com/pubnub/java/tree/v4.10.0) + September-17-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.9.1...v4.10.0) + +- 🌟rework the loading of services to load the classes once. + + + + +## [v4.9.1](https://github.com/pubnub/java/tree/v4.9.1) + August-14-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.9.0...v4.9.1) + +- 🌟patch-up to the deduping algorithm + + + + +## [v4.9.0](https://github.com/pubnub/java/tree/v4.9.0) + August-14-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.8.0...v4.9.0) + +- 🌟Internal deduping mechanism when devices cross regions (dedupOnSubscribe). + + + + +## [v4.8.0](https://github.com/pubnub/java/tree/v4.8.0) + August-08-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.7.0...v4.8.0) + +- 🌟Allow certificate pinning via setCertificatePinner on PNConfiguration + + +- 🌟Allow disabling of heartbeat by setting the interval to 0. + + +- 🌟GAE fixes. + + + + +## [v4.7.0](https://github.com/pubnub/java/tree/v4.7.0) + July-20-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.5...v4.7.0) + +- 🌟Allow injection of httpLoggingInterceptor for extra logging monitoring.. + + + + +## [v4.6.5](https://github.com/pubnub/java/tree/v4.6.5) + June-28-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.4...v4.6.5) + + + +- 🐛adjust queue exceeded notifications to be greater or equal of. + + +## [v4.6.4](https://github.com/pubnub/java/tree/v4.6.4) + June-10-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.3...v4.6.4) + + + +- 🐛gracefully handle disabled history + + +## [v4.6.3](https://github.com/pubnub/java/tree/v4.6.3) + June-03-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.2...v4.6.3) + +- 🌟on interval events, pass hereNowRefresh to indicate if a here_now fetch is needed. + + + + +## [v4.6.2](https://github.com/pubnub/java/tree/v4.6.2) + April-13-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.1...v4.6.2) + +- 🌟set a name for Subscription Manager Consumer Thead. + + + + +## [v4.6.1](https://github.com/pubnub/java/tree/v4.6.1) + April-06-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.6.0...v4.6.1) + + + +- 🐛SDK crash in Android with Airplane Mode +- 🌟add deltas on interval action. + + + + +## [v4.6.0](https://github.com/pubnub/java/tree/v4.6.0) + March-14-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.5.0...v4.6.0) + +- 🌟To distinguish UUID's that were generated by our SDK, we appended `pn-` before the UUID to signal that it's a randomly generated UUID. + + +- 🌟Allow the passing of okHttp connection spec via setConnectionSpec + + + +- ⭐Bump retrofit to 2.2.0 + + + +## [v4.5.0](https://github.com/pubnub/java/tree/v4.5.0) + February-15-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.4.4...v4.5.0) + +- 🌟add .toString methods to all public facing models and POJOs + + + + +## [v4.4.4](https://github.com/pubnub/java/tree/v4.4.4) + February-06-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.4.3...v4.4.4) + +- 🌟Add support to configure host name verifier. + + + + +## [v4.4.3](https://github.com/pubnub/java/tree/v4.4.3) + February-02-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.4.2...v4.4.3) + +- 🌟Add support to configure custom certificate pinning via SSLSocketFactory and X509 configuration objects. + + + + +## [v4.4.2](https://github.com/pubnub/java/tree/v4.4.2) + January-31-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.4.1...v4.4.2) + + + +- 🐛SDK was not sending the user metadata on Message Callback + + +## [v4.4.1](https://github.com/pubnub/java/tree/v4.4.1) + January-25-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.4.0...v4.4.1) + + + +- 🐛SDK did not honor the exhaustion of reconnections, it will now disconnect once max retries happened + + +## [v4.4.0](https://github.com/pubnub/java/tree/v4.4.0) + January-24-2017 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.3.1...v4.4.0) + + +- ⭐Support for maximum reconnection attempts + + +- ⭐Populate affectedChannel and affectedChannelGroups + + +- ⭐Support for GAE + + +- ⭐Emit pnconnected when adding / removing channels. + + + +## [v4.3.1](https://github.com/pubnub/java/tree/v4.3.1) + December-22-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.3.0...v4.3.1) + + +- ⭐support for key-level grant. + + + +## [v4.3.0](https://github.com/pubnub/java/tree/v4.3.0) + December-14-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.2.3...v4.3.0) + + +- ⭐JSON parser is switched to GSON, new artifact on nexus as pubnub-gson + + +- ⭐GetState, setState return a JsonElement instead of a plain object. + + + +## [v4.2.3](https://github.com/pubnub/java/tree/v4.2.3) + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.2.2...v4.2.3) + + +- ⭐Swapping out logger for slf4japi and removing final methods + + + +## [v4.2.2](https://github.com/pubnub/java/tree/v4.2.2) + December-09-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.2.1...v4.2.2) + + +- ⭐remove final identifiers from the public facing API. + + + +## [v4.2.1](https://github.com/pubnub/java/tree/v4.2.1) + November-23-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.2.0...v4.2.1) + + +- ⭐include publisher UUID on incoming message + + +- ⭐allow to set custom TTL on a publish + + + +## [v4.2.0](https://github.com/pubnub/java/tree/v4.2.0) + October-25-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.1.0...v4.2.0) + + +- ⭐Signatures are generated for all requests with secret key to ensure secure transmission of data + + +- ⭐support for alerting of queue exceeded (PNRequestMessageCountExceededCategory) + + +- ⭐signaling to okhttp to stop the queues on termination. + + + +## [v4.1.0](https://github.com/pubnub/java/tree/v4.1.0) + October-12-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.14...v4.1.0) + + +- ⭐destory now correctly forces the producer thread to shut down; stop is now deprecated for disconnect + + +- ⭐support for sending instance id for presence detection (disabled by default) + + +- ⭐support for sending request id to burst cache (enabled by default) + + +- ⭐proxy support via the native proxy configurator class. + + + +## [v4.0.14](https://github.com/pubnub/java/tree/v4.0.14) + September-20-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.13...v4.0.14) + + +- ⭐on PAM error, populate the affectedChannel or affectedChannelGroup to signal which channels are failing + + + +## [v4.0.13](https://github.com/pubnub/java/tree/v4.0.13) + September-14-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.12...v4.0.13) + + +- ⭐populate jso with the error. + + + +## [v4.0.12](https://github.com/pubnub/java/tree/v4.0.12) + September-13-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.11...v4.0.12) + + + +- 🐛fixing parsing of origination payload within the psv2 enevelope + + +## [v4.0.11](https://github.com/pubnub/java/tree/v4.0.11) + September-09-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.10...v4.0.11) + + +- ⭐bumping build process for gradle 3 / merging documentation into the repo and test adjustments + + + +## [v4.0.10](https://github.com/pubnub/java/tree/v4.0.10) + September-07-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.9...v4.0.10) + + +- ⭐adding channel / channelGroup fields when a message / presence event comes in. + + + +## [v4.0.9](https://github.com/pubnub/java/tree/v4.0.9) + August-24-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.8...v4.0.9) + + +- ⭐adjustments for handling pn_other and decryption + + +- ⭐retrofit version bumps. + + + +## [v4.0.8](https://github.com/pubnub/java/tree/v4.0.8) + August-16-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.7...v4.0.8) + +- 🌟added unsubscribeAll, getSubscribedChannels, getSubscribedChannelGroups + + +- 🌟SDK will establish secure connections by default + + +- 🌟added support for exponential backoff reconnection policies + + + + +## [v4.0.7](https://github.com/pubnub/java/tree/v4.0.7) + August-11-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.6...v4.0.7) + + +- ⭐reduce overlap on error handling when returning exceptions. + + + +## [v4.0.6](https://github.com/pubnub/java/tree/v4.0.6) + July-18-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.5...v4.0.6) + + +- ⭐send heartbeat presence value when subscribing + + + +## [v4.0.5](https://github.com/pubnub/java/tree/v4.0.5) + July-07-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.4...v4.0.5) + + +- ⭐unified retrofit handling to lower amount of instances and sync'd the state methods. + + + +## [v4.0.4](https://github.com/pubnub/java/tree/v4.0.4) + June-24-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.3...v4.0.4) + + + +- 🐛setting State for other UUID's is now supported. + + +## [v4.0.3](https://github.com/pubnub/java/tree/v4.0.3) + June-15-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.2...v4.0.3) + +- 🌟fire() method and no-replicaton options. + + + + +## [v4.0.2](https://github.com/pubnub/java/tree/v4.0.2) + June-15-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.1...v4.0.2) + + + +- 🐛fix to the version fetching. + + +## [v4.0.1](https://github.com/pubnub/java/tree/v4.0.1) + June-06-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.0...v4.0.1) + + + +- 🐛adjustment of the subscribe loop to alleviate duplicate dispatches. + + +## [v4.0.0](https://github.com/pubnub/java/tree/v4.0.0) + June-03-2016 + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.0-beta4...v4.0.0) + + + +- 🐛first GA. + + +## [v4.0.0-beta4](https://github.com/pubnub/java/tree/v4.0.0-beta4) + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.0-beta3...v4.0.0-beta4) + + +- ⭐reconnects and minor adjustments. + + + +## [v4.0.0-beta3](https://github.com/pubnub/java/tree/v4.0.0-beta3) + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.0-beta2...v4.0.0-beta3) + + + +- 🐛fixing state not coming on the subscriber callback. + + +- 🐛adjustments to URL encoding on publish, subscribe, set-state operations to avoid double encoding with retrofit. + + +## [v4.0.0-beta2](https://github.com/pubnub/java/tree/v4.0.0-beta2) + + + [Full Changelog](https://github.com/pubnub/java/compare/v4.0.0-beta1...v4.0.0-beta2) + + +- ⭐reworking of message queue. + + +- ⭐checkstyle, findbugs. + + +- ⭐reworking error notifications. + + + +## [v4.0.0-beta1](https://github.com/pubnub/java/tree/v4.0.0-beta1) + + + + +- ⭐initial beta1. diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 000000000..0d4f663cf --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,26 @@ + +### Installing Dependencies + * Gradle [https://docs.gradle.org/current/userguide/installation.html] + * Lombok Plugins: + * [intellij](https://plugins.jetbrains.com/plugin/6317) -- [installation guide](https://github.com/mplushnikov/lombok-intellij-plugin#installation) + * [eclipse](http://stackoverflow.com/questions/22310414/how-to-configure-lombok-in-eclipse-luna) + +### Adding Dependencies + * File -> Project Structure -> Modules -> Main/Test -> Dependencies (tab) -> "+" (add) -> Library -> Java + * Select and add all libraries from list + * Apply changes + +### Compiling + `gradle clean compile` + +### Building a shadowJar (Fat Jar) + * `gradle clean build shadowJar` + ##### or + * `gradle clean` + * `gradle clean test` + * `gradle build shadowJar` + +### deploying to nexus + * enable the javadoc documentation + * `gradle clean build javadoc upload` + * enable the new package on sonatype diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..3efa3922e --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +Copyright (c) 2013 PubNub Inc. +http://www.pubnub.com/ +http://www.pubnub.com/terms + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks +Copyright (c) 2013 PubNub Inc. +http://www.pubnub.com/ +http://www.pubnub.com/terms diff --git a/README.md b/README.md new file mode 100644 index 000000000..60f83dfdd --- /dev/null +++ b/README.md @@ -0,0 +1,207 @@ +# PubNub Java-based SDKs for Java / Android + +[![Build Status](https://travis-ci.com/pubnub/java.svg?branch=master)](https://travis-ci.com/pubnub/java) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/164fd518c314417e896b3de494ab75df)](https://www.codacy.com/app/PubNub/java?utm_source=github.com&utm_medium=referral&utm_content=pubnub/java&utm_campaign=Badge_Grade) +[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/164fd518c314417e896b3de494ab75df)](https://www.codacy.com/app/PubNub/java?utm_source=github.com&utm_medium=referral&utm_content=pubnub/java&utm_campaign=Badge_Coverage) +[![Download](https://api.bintray.com/packages/bintray/jcenter/com.pubnub%3Apubnub-gson/images/download.svg)](https://bintray.com/bintray/jcenter/com.pubnub%3Apubnub-gson/_latestVersion) +[![Maven Central](https://img.shields.io/maven-central/v/com.pubnub/pubnub-gson.svg)]() + +This is the official PubNub Java SDK repository. + +PubNub takes care of the infrastructure and APIs needed for the realtime communication layer of your application. Work on your app's logic and let PubNub handle sending and receiving data across the world in less than 100ms. + +## Get keys + +You will need the publish and subscribe keys to authenticate your app. Get your keys from the [Admin Portal](https://dashboard.pubnub.com/login). + +## Configure PubNub + +1. Integrate the Java SDK into your project: + + * for Maven, add the following dependency in your `pom.xml`: + ```xml + + com.pubnub + pubnub-gson + 5.2.1 + + ``` + + * for Gradle, add the following dependency in your `gradle.build`: + ```groovy + compile group: 'com.pubnub', name: 'pubnub-gson', version: '5.2.1' + ``` + +2. Configure your keys: + + ```java + PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey("mySubscribeKey"); + pnConfiguration.setPublishKey("myPublishKey"); + pnConfiguration.setUuid("myUniqueUUID"); + + PubNub pubnub = new PubNub(pnConfiguration); + ``` + +## Add event listeners + +```java +// SubscribeCallback is an Abstract Java class. It requires that you implement all Abstract methods of the parent class even if you don't need all the handler methods. + +pubnub.addListener(new SubscribeCallback() { + // PubNub status + @Override + public void status(PubNub pubnub, PNStatus status) { + switch (status.getOperation()) { + // combine unsubscribe and subscribe handling for ease of use + case PNSubscribeOperation: + case PNUnsubscribeOperation: + // Note: subscribe statuses never have traditional errors, + // just categories to represent different issues or successes + // that occur as part of subscribe + switch (status.getCategory()) { + case PNConnectedCategory: + // No error or issue whatsoever. + case PNReconnectedCategory: + // Subscribe temporarily failed but reconnected. + // There is no longer any issue. + case PNDisconnectedCategory: + // No error in unsubscribing from everything. + case PNUnexpectedDisconnectCategory: + // Usually an issue with the internet connection. + // This is an error: handle appropriately. + case PNAccessDeniedCategory: + // PAM does not allow this client to subscribe to this + // channel and channel group configuration. This is + // another explicit error. + default: + // You can directly specify more errors by creating + // explicit cases for other error categories of + // `PNStatusCategory` such as `PNTimeoutCategory` or + // `PNMalformedFilterExpressionCategory` or + // `PNDecryptionErrorCategory`. + } + + case PNHeartbeatOperation: + // Heartbeat operations can in fact have errors, + // so it's important to check first for an error. + // For more information on how to configure heartbeat notifications + // through the status PNObjectEventListener callback, refer to + // /docs/android-java/api-reference-configuration#configuration_basic_usage + if (status.isError()) { + // There was an error with the heartbeat operation, handle here + } else { + // heartbeat operation was successful + } + default: { + // Encountered unknown status type + } + } + } + + // Messages + @Override + public void message(PubNub pubnub, PNMessageResult message) { + String messagePublisher = message.getPublisher(); + System.out.println("Message publisher: " + messagePublisher); + System.out.println("Message Payload: " + message.getMessage()); + System.out.println("Message Subscription: " + message.getSubscription()); + System.out.println("Message Channel: " + message.getChannel()); + System.out.println("Message timetoken: " + message.getTimetoken()); + } + + // Presence + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + System.out.println("Presence Event: " + presence.getEvent()); + // Can be join, leave, state-change or timeout + + System.out.println("Presence Channel: " + presence.getChannel()); + // The channel to which the message was published + + System.out.println("Presence Occupancy: " + presence.getOccupancy()); + // Number of users subscribed to the channel + + System.out.println("Presence State: " + presence.getState()); + // User state + + System.out.println("Presence UUID: " + presence.getUuid()); + // UUID to which this event is related + + presence.getJoin(); + // List of users that have joined the channel (if event is 'interval') + + presence.getLeave(); + // List of users that have left the channel (if event is 'interval') + + presence.getTimeout(); + // List of users that have timed-out off the channel (if event is 'interval') + + presence.getHereNowRefresh(); + // Indicates to the client that it should call 'hereNow()' to get the + // complete list of users present in the channel. + } + + // Signals + @Override + public void signal(PubNub pubnub, PNSignalResult pnSignalResult) { + System.out.println("Signal publisher: " + signal.getPublisher()); + System.out.println("Signal payload: " + signal.getMessage()); + System.out.println("Signal subscription: " + signal.getSubscription()); + System.out.println("Signal channel: " + signal.getChannel()); + System.out.println("Signal timetoken: " + signal.getTimetoken()); + } + + // Message actions + @Override + public void messageAction(PubNub pubnub, PNMessageActionResult pnActionResult) { + PNMessageAction pnMessageAction = pnActionResult.getAction(); + System.out.println("Message action type: " + pnMessageAction.getType()); + System.out.println("Message action value: " + pnMessageAction.getValue()); + System.out.println("Message action uuid: " + pnMessageAction.getUuid()); + System.out.println("Message action actionTimetoken: " + pnMessageAction.getActionTimetoken()); + System.out.println("Message action messageTimetoken: " + pnMessageAction.getMessageTimetoken());] + System.out.println("Message action subscription: " + pnActionResult.getSubscription()); + System.out.println("Message action channel: " + pnActionResult.getChannel()); + System.out.println("Message action timetoken: " + pnActionResult.getTimetoken()); + } + + // Files + @Override + public void file(PubNub pubnub, PNFileEventResult pnFileEventResult) { + System.out.println("File channel: " + pnFileEventResult.getChannel()); + System.out.println("File publisher: " + pnFileEventResult.getPublisher()); + System.out.println("File message: " + pnFileEventResult.getMessage()); + System.out.println("File timetoken: " + pnFileEventResult.getTimetoken()); + System.out.println("File file.id: " + pnFileEventResult.getFile().getId()); + System.out.println("File file.name: " + pnFileEventResult.getFile().getName()); + System.out.println("File file.url: " + pnFileEventResult.getFile().getUrl()); + } +}); +``` + +## Publish/subscribe + +```java +pubnub.publish().channel(channelName) + .message(messageJsonObject) + .async((result, publishStatus) -> { + if (!publishStatus.isError()) { + // Message successfully published to specified channel. + } else { // Request processing failed. + // Handle message publish error + // Check 'category' property to find out + // issues because of which the request failed. + // Request can be resent using: [status retry]; + } +}); +``` + +## Documentation + +* [API reference for Java ](https://www.pubnub.com/docs/java-se-java/pubnub-java-sdk) +* [API reference for Android](https://www.pubnub.com/docs/android-java/pubnub-java-sdk) + +## Support + +If you **need help** or have a **general question**, contact support@pubnub.com. diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..c48647810 --- /dev/null +++ b/build.gradle @@ -0,0 +1,243 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.72' + id 'io.franzbecker.gradle-lombok' version '1.14' + id 'com.github.johnrengelman.shadow' version '4.0.2' + id 'com.bmuschko.nexus' version '2.3.1' + id 'com.github.ben-manes.versions' version '0.20.0' + id 'java-library' + id 'jacoco' + id 'maven' + id 'checkstyle' + id 'findbugs' +} +group = 'com.pubnub' + +version = '5.2.1' + +description = """""" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + + +configurations.all { +} + +configurations { + cucumberRuntime { + extendsFrom testImplementation + } +} + +task cucumber() { + dependsOn assemble, testClasses + group = "verification" + doLast { + javaexec { + Properties localProperties = new Properties() + try { + localProperties.load(new FileInputStream(rootProject.file("test.properties"))) + } + catch (Exception e) { + localProperties.putAll(System.getenv()) + } + main = "io.cucumber.core.cli.Main" + classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output + + if (localProperties['cucumber.tags'] != null) { + args = ['--tags', localProperties['cucumberTags'], + '--plugin', 'pretty', + '--plugin', 'json:build/reports/cucumber-reports/Cucumber.json', + '--plugin', 'junit:build/reports/cucumber-reports/Cucumber.xml', + '--plugin', 'html:build/reports/cucumber-reports/Cucumber.html', + '--glue', 'com.pubnub.contract', + localProperties['featuresDir']] + } else { + args = ['--plugin', 'pretty', + '--plugin', 'json:build/reports/cucumber-reports/Cucumber.json', + '--plugin', 'junit:build/reports/cucumber-reports/Cucumber.xml', + '--plugin', 'html:build/reports/cucumber-reports/Cucumber.html', + '--glue', 'com.pubnub.contract', + localProperties['featuresDir']] + } + } + } +} + +lombok { + version = "1.18.4" +} + +repositories { + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } +} + +sourceSets { + integrationTest { + compileClasspath += sourceSets.test.runtimeClasspath + runtimeClasspath += sourceSets.test.runtimeClasspath + } +} + +dependencies { + implementation group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.6.2' + api group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.12.6' + + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28' + + // jackson + // compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version:'2.7.3' + // compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version:'2.7.3' + // compile group: 'com.squareup.retrofit2', name: 'converter-jackson', version:'2.1.0' + + // gson + api 'com.google.code.gson:gson:2.8.6' + implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.6.2' + + // cbor + implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.3' + + implementation 'org.jetbrains:annotations:17.0.0' + + testCompile group: 'org.mockito', name: 'mockito-core', version: '3.3.3' + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + testImplementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3' + testImplementation group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3' + testImplementation group: 'junit', name: 'junit', version: '4.12' + testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.25.0' + testImplementation group: 'org.awaitility', name: 'awaitility', version: '4.0.1' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.6.0' + integrationTestImplementation group: 'org.aeonbits.owner', name: 'owner', version: '1.0.8' + implementation group: 'org.json', name: 'json', version: '20190722' + testImplementation group: 'io.cucumber', name: 'cucumber-java', version: '6.10.4' + testImplementation group: 'io.cucumber', name: 'cucumber-junit', version: '6.10.4' + testImplementation group: 'io.cucumber', name: 'cucumber-picocontainer', version: '6.10.4' + testImplementation group: 'org.aeonbits.owner', name: 'owner', version: '1.0.8' + testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" +} + +task integrationTest(type: Test) { + group = "verification" + testClassesDir = sourceSets.integrationTest.output.classesDir + classpath += sourceSets.integrationTest.runtimeClasspath +} + +jacoco { + toolVersion = "0.8.2" +} + +jacocoTestReport { + reports { + xml.enabled = true + html.enabled = true + } +} + +checkstyle { + toolVersion = "8.14" + configFile = rootProject.file('config/checkstyle/checkstyle.xml') + //configFile = new File(rootDir, "config/checkstyle/checkstyle.xml") + sourceSets = [sourceSets.main] +} + +findbugs { + excludeFilter = rootProject.file("config/findbugs/excludeFilter.xml") + sourceSets = [sourceSets.main] +} + +tasks.withType(Checkstyle) { + exclude '**/vendor/**', '**/*Test*' + + reports { + xml.enabled = true + html.enabled = true + } +} + +tasks.withType(FindBugs) { + exclude '**/vendor/**', '**/*Test*' + + reports { + xml.enabled false + html.enabled true + } +} + +check.dependsOn jacocoTestReport + +extraArchive { + sources = false + tests = true + javadoc = true +} + +nexus { + sign = true + repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' + snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots' +} + +modifyPom { + project { + name 'PubNub Java SDK' + description 'PubNub is a cross-platform client-to-client (1:1 and 1:many) push service in the cloud, capable of\n' + + ' broadcasting real-time messages to millions of web and mobile clients simultaneously, in less than a quarter\n' + + ' second!' + url 'https://github.com/pubnub/java' + inceptionYear '2009' + + scm { + url 'https://github.com/pubnub/java' + } + + licenses { + license { + name 'MIT License' + url 'https://github.com/pubnub/pubnub-api/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'PubNub' + name 'PubNub' + email 'support@pubnub.com' + } + } + } +} + +import io.franzbecker.gradle.lombok.task.DelombokTask + +task delombok(type: DelombokTask, dependsOn: compileJava) { + ext.outputDir = file("$buildDir/delombok") + outputs.dir(outputDir) + sourceSets.main.java.srcDirs.each { + inputs.dir(it) + args(it, "-d", outputDir) + } +} + +task delombokHelp(type: DelombokTask) { + args "--help" +} + +javadoc { + dependsOn delombok + source = delombok.outputDir + destinationDir = file("docs") + + options.noTimestamp = true +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from "$buildDir/delombok" +} + +build.finalizedBy(shadowJar) diff --git a/codacy-coverage-reporter-2.0.0-assembly.jar b/codacy-coverage-reporter-2.0.0-assembly.jar new file mode 100644 index 000000000..5942ec165 Binary files /dev/null and b/codacy-coverage-reporter-2.0.0-assembly.jar differ diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..582e97e0b --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/findbugs/excludeFilter.xml b/config/findbugs/excludeFilter.xml new file mode 100644 index 000000000..b1698440c --- /dev/null +++ b/config/findbugs/excludeFilter.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..29953ea14 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..5fc459491 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 17 12:17:22 CEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..dd48b49e5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pubnub-gson' diff --git a/src/integrationTest/README.md b/src/integrationTest/README.md new file mode 100644 index 000000000..f72a31fda --- /dev/null +++ b/src/integrationTest/README.md @@ -0,0 +1,14 @@ +## Running integration test + +Java SDK source contains source set with integration tests that can be run against real PubNub application. +In order to run integration tests make sure the dedicated App is created on [PubNub's Admin Portal](https://admin.pubnub.com/). +To launch integration tests use Gradle's `integrationTest` task. +Before doing this make sure correct Subscribe Key is configured in `test.properties` file in project's root directory. +See contents of `test.properties.example` for more details. + +Parameters from `test.properties` file can be overriden by setting corresponding environment variables. Example: +```$bash +> export subKey= +> ./gradlew build integrationTest +``` +In current version those tests are not plugged into CI pipeline and they need to be launched on demand. diff --git a/src/integrationTest/java/com/pubnub/api/integration/FilesIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/FilesIntegrationTests.java new file mode 100644 index 000000000..1c983b8a7 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/FilesIntegrationTests.java @@ -0,0 +1,174 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.files.PNDownloadFileResult; +import com.pubnub.api.models.consumer.files.PNFileUploadResult; +import com.pubnub.api.models.consumer.files.PNListFilesResult; +import com.pubnub.api.models.consumer.files.PNUploadedFile; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Scanner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static com.pubnub.api.integration.util.Utils.randomChannel; + +public class FilesIntegrationTests extends BaseIntegrationTest { + + @Test + public void uploadListDownloadDeleteWithCipher() throws PubNubException, InterruptedException, IOException { + doItAllFilesTest(true); + } + + @Test + public void uploadListDownloadDeleteWithoutCipher() throws PubNubException, InterruptedException, IOException { + doItAllFilesTest(false); + } + + public void doItAllFilesTest(boolean withCipher) throws PubNubException, InterruptedException, IOException { + if (withCipher) { + pubNub.getConfiguration().setCipherKey("enigma"); + } else { + pubNub.getConfiguration().setCipherKey(null); + } + String channel = randomChannel(); + String content = "This is content"; + String message = "This is message"; + String meta = "This is meta"; + String fileName = "fileName" + channel + ".txt"; + CountDownLatch connectedLatch = new CountDownLatch(1); + CountDownLatch fileEventReceived = new CountDownLatch(1); + + pubNub.addListener(new LimitedListener() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + if (pnStatus.getCategory() == PNStatusCategory.PNConnectedCategory) { + connectedLatch.countDown(); + } + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + if (pnFileEventResult.getFile().getName().equals(fileName)) { + fileEventReceived.countDown(); + } + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + PNFileUploadResult sendResult; + connectedLatch.await(10, TimeUnit.SECONDS); + try (InputStream is = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) { + sendResult = pubNub.sendFile() + .channel(channel) + .fileName(fileName) + .inputStream(is) + .message(message) + .meta(meta) + .sync(); + } + + + fileEventReceived.await(10, TimeUnit.SECONDS); + PNListFilesResult listedFiles = pubNub.listFiles() + .channel(channel) + .sync(); + + boolean fileFoundOnList = false; + for (PNUploadedFile f : listedFiles.getData()) { + if (f.getId().equals(sendResult.getFile().getId())) { + fileFoundOnList = true; + break; + } + } + Assert.assertTrue(fileFoundOnList); + + PNDownloadFileResult downloadResult = pubNub + .downloadFile() + .channel(channel) + .fileName(fileName) + .fileId(sendResult.getFile().getId()) + .sync(); + + try (InputStream is = downloadResult.getByteStream()) { + Assert.assertEquals(content, readToString(is)); + } + + + pubNub.deleteFile() + .channel(channel) + .fileName(fileName) + .fileId(sendResult.getFile().getId()) + .sync(); + } + + private String readToString(InputStream inputStream) { + try (Scanner s = new Scanner(inputStream).useDelimiter("\\A")) { + return s.hasNext() ? s.next() : ""; + } + } + + private abstract static class LimitedListener extends SubscribeCallback { + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + } + + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/GroupManagementIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/GroupManagementIntegrationTests.java new file mode 100644 index 000000000..16e5da357 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/GroupManagementIntegrationTests.java @@ -0,0 +1,155 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.integration.util.BaseIntegrationTest; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; + +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.*; + +public class GroupManagementIntegrationTests extends BaseIntegrationTest { + + private String mChannel1; + private String mChannel2; + private String mChannel3; + + private String mGroup; + + @Override + protected void onBefore() { + mChannel1 = randomChannel(); + mChannel2 = randomChannel(); + mChannel3 = randomChannel(); + mGroup = "cg_".concat(random()); + } + + @Override + protected void onAfter() { + + } + + @Test + public void testRemoveChannelsFromGroup() throws InterruptedException { + final CountDownLatch signal = new CountDownLatch(1); + + addChannelsToGroup(); + + pubNub.removeChannelsFromChannelGroup() + .channelGroup(mGroup) + .channels(Arrays.asList(mChannel1, mChannel2, mChannel3)) + .async((result, status) -> { + assertFalse(status.isError()); + assert status.getAffectedChannels() != null; + assertEquals(3, status.getAffectedChannels().size()); + assertEquals(0, pubNub.getSubscribedChannels().size()); + assert status.getAffectedChannelGroups() != null; + assertEquals(1, status.getAffectedChannelGroups().size()); + assertEquals(0, pubNub.getSubscribedChannelGroups().size()); + signal.countDown(); + }); + + signal.await(); + } + + @Test + public void testRemoveChannelFromGroup() throws InterruptedException { + final CountDownLatch signal = new CountDownLatch(1); + + addChannelsToGroup(); + + pubNub.removeChannelsFromChannelGroup() + .channelGroup(mGroup) + .channels(Collections.singletonList(mChannel1)) + .async((result, status) -> { + assertFalse(status.isError()); + signal.countDown(); + }); + + signal.await(); + } + + @Test + public void testSubscribeToChannelGroup() throws InterruptedException { + addChannelsToGroup(); + subscribeToChannelGroup(pubNub, mGroup); + + boolean isGroupSubscribed = false; + for (int i = 0; i < pubNub.getSubscribedChannelGroups().size(); i++) { + if (pubNub.getSubscribedChannelGroups().get(i).equals(mGroup)) { + isGroupSubscribed = true; + } + } + + assertTrue(isGroupSubscribed); + } + + @Test + public void testUnsubscribeFromChannelGroup() throws InterruptedException { + addChannelsToGroup(); + subscribeToChannelGroup(pubNub, mGroup); + + pubNub.unsubscribe() + .channelGroups(Collections.singletonList(mGroup)) + .execute(); + + pause(1); + + assertEquals(0, pubNub.getSubscribedChannelGroups().size()); + } + + @Test + public void testGetAllChannelsFromGroup() throws InterruptedException { + final CountDownLatch signal = new CountDownLatch(1); + + addChannelsToGroup(); + + pubNub.listChannelsForChannelGroup() + .channelGroup(mGroup) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(3, result.getChannels().size()); + signal.countDown(); + }); + + signal.await(); + } + + @Test + public void testAddChannelsToGroup() throws InterruptedException { + final CountDownLatch signal = new CountDownLatch(1); + + pubNub.addChannelsToChannelGroup() + .channelGroup(mGroup) + .channels(Arrays.asList(mChannel1, mChannel2, mChannel3)) + .async((result, status) -> { + assertFalse(status.isError()); + assert status.getAffectedChannelGroups() != null; + assertEquals(1, status.getAffectedChannelGroups().size()); + assert status.getAffectedChannels() != null; + assertEquals(3, status.getAffectedChannels().size()); + signal.countDown(); + }); + + signal.await(); + } + + private void addChannelsToGroup() throws InterruptedException { + final CountDownLatch signal = new CountDownLatch(1); + + pubNub.addChannelsToChannelGroup() + .channelGroup(mGroup) + .channels(Arrays.asList(mChannel1, mChannel2, mChannel3)) + .async((result, status) -> { + assertFalse(status.isError()); + signal.countDown(); + }); + + signal.await(); + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/HeartbeatIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/HeartbeatIntegrationTest.java new file mode 100644 index 000000000..42405a301 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/HeartbeatIntegrationTest.java @@ -0,0 +1,155 @@ +package com.pubnub.api.integration; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.hamcrest.core.IsEqual; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.time.Duration; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class HeartbeatIntegrationTest extends BaseIntegrationTest { + + private String expectedChannel; + + @Override + protected void onBefore() { + expectedChannel = randomChannel(); + } + + @Override + protected void onAfter() { + + } + + @Test + public void testStateWithHeartbeat() { + final AtomicInteger hits = new AtomicInteger(); + final JsonObject expectedStatePayload = generatePayload(); + final PubNub observer = getPubNub(); + + pubNub.getConfiguration().setPresenceTimeoutWithCustomInterval(20, 4); + + observer.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pn, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().contains(expectedChannel)) { + + pubNub.subscribe() + .channels(Collections.singletonList(expectedChannel)) + .withPresence() + .execute(); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub p, @NotNull PNPresenceEventResult presence) { + if (presence.getUuid().equals(pubNub.getConfiguration().getUuid()) + && presence.getChannel().equals(expectedChannel)) { + switch (presence.getEvent()) { + case "state-change": + assertEquals(expectedStatePayload, presence.getState()); + hits.incrementAndGet(); + pubNub.disconnect(); + break; + case "join": + if (presence.getState() == null) { + hits.incrementAndGet(); + + final AtomicBoolean stateSet = new AtomicBoolean(); + + pubNub.setPresenceState() + .state(expectedStatePayload) + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(expectedStatePayload, result.getState()); + hits.incrementAndGet(); + stateSet.set(true); + }); + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilTrue(stateSet); + + } else { + assertEquals(expectedStatePayload, presence.getState()); + hits.incrementAndGet(); + } + break; + case "timeout": + pubNub.reconnect(); + break; + } + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + observer.subscribe() + .channels(Collections.singletonList(expectedChannel)) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(Duration.ofSeconds(40)) + .untilAtomic(hits, IsEqual.equalTo(4)); + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/HistoryIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/HistoryIntegrationTest.java new file mode 100644 index 000000000..f845d9c17 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/HistoryIntegrationTest.java @@ -0,0 +1,604 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.history.PNFetchMessageItem; +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult; +import com.pubnub.api.models.consumer.history.PNHistoryItemResult; +import com.pubnub.api.models.consumer.history.PNHistoryResult; +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS; +import static com.pubnub.api.integration.util.Utils.publishMixed; +import static com.pubnub.api.integration.util.Utils.queryParam; +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class HistoryIntegrationTest extends BaseIntegrationTest { + + @Test + public void testHistorySingleChannel() throws PubNubException { + final String expectedChannelName = randomChannel(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, + expectedMessageCount, + expectedChannelName).size()); + + final PNHistoryResult historyResult = pubNub.history() + .channel(expectedChannelName) + .sync(); + + assert historyResult != null; + for (PNHistoryItemResult message : historyResult.getMessages()) { + assertNotNull(message.getEntry()); + assertNull(message.getMeta()); + assertNull(message.getTimetoken()); + } + } + + @Test + public void testHistorySingleChannel_TimeToken() throws PubNubException { + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, expectedMessageCount, expectedChannelName).size()); + + final PNHistoryResult historyResult = pubNub.history() + .channel(expectedChannelName) + .includeTimetoken(true) + .sync(); + + assert historyResult != null; + for (PNHistoryItemResult message : historyResult.getMessages()) { + assertNotNull(message.getEntry()); + assertNotNull(message.getTimetoken()); + assertNull(message.getMeta()); + } + } + + @Test + public void testHistorySingleChannel_Meta() throws PubNubException { + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, expectedMessageCount, expectedChannelName).size()); + + final PNHistoryResult historyResult = pubNub.history() + .channel(expectedChannelName) + .includeMeta(true) + .sync(); + + assert historyResult != null; + for (PNHistoryItemResult message : historyResult.getMessages()) { + assertNotNull(message.getEntry()); + assertNull(message.getTimetoken()); + assertNotNull(message.getMeta()); + } + } + + @Test + public void testHistorySingleChannel_Meta_Timetoken() throws PubNubException { + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, expectedMessageCount, expectedChannelName).size()); + + final PNHistoryResult historyResult = pubNub.history() + .channel(expectedChannelName) + .includeMeta(true) + .includeTimetoken(true) + .sync(); + + assert historyResult != null; + for (PNHistoryItemResult message : historyResult.getMessages()) { + assertNotNull(message.getEntry()); + assertNotNull(message.getTimetoken()); + assertNotNull(message.getMeta()); + } + } + + @Test + public void testFetchSingleChannel() throws PubNubException { + final String expectedChannelName = random(); + + publishMixed(pubNub, 10, expectedChannelName); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .maximumPerChannel(25) + .sync(); + + pause(3); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNull(messageItem.getMeta()); + assertNull(messageItem.getActions()); + } + + } + + @Test + public void testFetchSingleChannel_Meta() throws PubNubException { + final String expectedChannelName = random(); + + publishMixed(pubNub, 10, expectedChannelName); + + pause(3); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .maximumPerChannel(25) + .includeMeta(true) + .sync(); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNotNull(messageItem.getMeta()); + assertNull(messageItem.getActions()); + } + + } + + @Test + public void testFetchSingleChannel_Actions() throws PubNubException { + final String expectedChannelName = random(); + + final List results = publishMixed(pubNub, 120, expectedChannelName); + + pubNub.addMessageAction() + .channel(expectedChannelName) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(results.get(0).getTimetoken())) + .sync(); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .maximumPerChannel(25) + .includeMessageActions(true) + .includeMeta(false) + .sync(); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNull(messageItem.getMeta()); + if (messageItem.getTimetoken().equals(results.get(0).getTimetoken())) { + assertNotNull(messageItem.getActions()); + } else { + assertTrue(messageItem.getActions().isEmpty()); + } + } + } + + @Test + public void testFetchSingleChannel_ActionsMeta() throws PubNubException { + final String expectedChannelName = random(); + + final List results = publishMixed(pubNub, 2, expectedChannelName); + + pubNub.addMessageAction() + .channel(expectedChannelName) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(results.get(0).getTimetoken())) + .sync(); + + pause(3); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .maximumPerChannel(25) + .includeMessageActions(true) + .includeMeta(true) + .sync(); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNotNull(messageItem.getMeta()); + if (messageItem.getTimetoken().equals(results.get(0).getTimetoken())) { + assertNotNull(messageItem.getActions()); + } else { + assertTrue(messageItem.getActions().isEmpty()); + } + } + } + + @Test + public void testFetchMultiChannel() throws PubNubException { + final String[] expectedChannelNames = new String[]{ + random(), + random() + }; + + for (String expectedChannelName : expectedChannelNames) { + publishMixed(pubNub, 10, expectedChannelName); + } + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Arrays.asList(expectedChannelNames)) + .maximumPerChannel(25) + .sync(); + + for (String expectedChannelName : expectedChannelNames) { + assert fetchMessagesResult != null; + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNull(messageItem.getMeta()); + assertNull(messageItem.getActions()); + } + } + + } + + @Test + public void testFetchSingleChannel_NoLimit() throws PubNubException { + final String expectedChannelName = random(); + + assertEquals(10, publishMixed(pubNub, 10, expectedChannelName).size()); + + pause(3); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .sync(); + + assert fetchMessagesResult != null; + assertEquals(10, fetchMessagesResult.getChannels().get(expectedChannelName).size()); + + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNull(messageItem.getMeta()); + assertNull(messageItem.getActions()); + } + } + + @Test + public void testFetchSingleChannel_OverflowLimit() throws PubNubException { + final String expectedChannelName = random(); + + assertEquals(10, publishMixed(pubNub, 10, expectedChannelName).size()); + + pause(3); + + final PNFetchMessagesResult fetchMessagesResult = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .maximumPerChannel(100) + .sync(); + + assert fetchMessagesResult != null; + assertEquals(10, fetchMessagesResult.getChannels().get(expectedChannelName).size()); + + for (PNFetchMessageItem messageItem : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(messageItem.getMessage()); + assertNotNull(messageItem.getTimetoken()); + assertNull(messageItem.getMeta()); + assertNull(messageItem.getActions()); + } + } + + @Test + public void testHistorySingleChannel_IncludeAll_Crypto() throws PubNubException { + final String expectedCipherKey = random(); + pubNub.getConfiguration().setCipherKey(expectedCipherKey); + + final PubNub observer = getPubNub(); + observer.getConfiguration().setCipherKey(expectedCipherKey); + + assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); + + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, expectedMessageCount, expectedChannelName).size()); + + final PNHistoryResult historyResult = observer.history() + .channel(expectedChannelName) + .includeTimetoken(true) + .includeMeta(true) + .sync(); + + assert historyResult != null; + for (PNHistoryItemResult message : historyResult.getMessages()) { + assertNotNull(message.getEntry()); + assertNotNull(message.getTimetoken()); + assertNotNull(message.getMeta()); + assertTrue(message.getEntry().toString().contains("_msg")); + } + } + + @Test + public void testFetchSingleChannel_IncludeAll_Crypto() throws PubNubException { + final String expectedCipherKey = random(); + pubNub.getConfiguration().setCipherKey(expectedCipherKey); + + final PubNub observer = getPubNub(); + observer.getConfiguration().setCipherKey(expectedCipherKey); + + assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); + + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + assertEquals(expectedMessageCount, + publishMixed(pubNub, expectedMessageCount, expectedChannelName).size()); + + final PNFetchMessagesResult fetchMessagesResult = observer.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .includeMeta(true) + .sync(); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem message : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(message.getMessage()); + assertNotNull(message.getTimetoken()); + assertNotNull(message.getMeta()); + assertNull(message.getActions()); + assertTrue(message.getMessage().toString().contains("_msg")); + } + } + + @Test + public void testFetchSingleChannel_WithActions_IncludeAll_Crypto() throws PubNubException { + final String expectedCipherKey = random(); + pubNub.getConfiguration().setCipherKey(expectedCipherKey); + + final PubNub observer = getPubNub(); + observer.getConfiguration().setCipherKey(expectedCipherKey); + + assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); + + final String expectedChannelName = random(); + final int expectedMessageCount = 10; + + final List mixed = publishMixed(pubNub, expectedMessageCount, expectedChannelName); + assertEquals(expectedMessageCount, mixed.size()); + + final List messagesWithActions = new ArrayList<>(); + + for (int i = 0; i < mixed.size(); i++) { + if (i % 2 == 0) { + final PNAddMessageActionResult reaction = pubNub.addMessageAction() + .channel(expectedChannelName) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(mixed.get(i).getTimetoken())) + .sync(); + assert reaction != null; + messagesWithActions.add(reaction.getMessageTimetoken()); + } + } + + final PNFetchMessagesResult fetchMessagesResult = observer.fetchMessages() + .channels(Collections.singletonList(expectedChannelName)) + .includeMeta(true) + .includeMessageActions(true) + .sync(); + + assert fetchMessagesResult != null; + for (PNFetchMessageItem message : fetchMessagesResult.getChannels().get(expectedChannelName)) { + assertNotNull(message.getMessage()); + assertNotNull(message.getTimetoken()); + assertNotNull(message.getMeta()); + if (messagesWithActions.contains(message.getTimetoken())) { + assertNotNull(message.getActions()); + } else { + assertTrue(message.getActions().isEmpty()); + } + assertTrue(message.getMessage().toString().contains("_msg")); + } + } + + @Test + public void testFetchMultiChannel_WithMessageActions_Exception() { + try { + pubNub.fetchMessages() + .channels(Arrays.asList(random(), random())) + .includeMessageActions(true) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERROBJ_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS, e.getPubnubError()); + } + } + + @Test + public void testFetchSingleChannel_NoActions_Limit_Default() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .async((result, status) -> { + assertEquals("100", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_NoActions_Limit_Low() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .maximumPerChannel(-1) + .async((result, status) -> { + assertEquals("100", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_NoActions_Limit_Valid() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .maximumPerChannel(15) + .async((result, status) -> { + assertEquals("15", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_NoActions_Limit_High() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .maximumPerChannel(100) + .async((result, status) -> { + assertEquals("100", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_WithActions_Limit_Default() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .includeMessageActions(true) + .async((result, status) -> { + assertEquals("25", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_WithActions_Limit_Low() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .includeMessageActions(true) + .maximumPerChannel(-1) + .async((result, status) -> { + assertEquals("25", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_WithActions_Limit_High() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .includeMessageActions(true) + .maximumPerChannel(200) + .async((result, status) -> { + assertEquals("25", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + @Test + public void testFetchSingleChannel_WithActions_Limit_Valid() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(random())) + .includeMessageActions(true) + .maximumPerChannel(15) + .async((result, status) -> { + assertEquals("15", queryParam(status, "max")); + success.set(true); + }); + + listen(success); + } + + + @Test + public void testEmptyMeta() throws PubNubException { + final String channel = random(); + + // publish a message without any metadata + pubNub.publish() + .message(random()) + .channel(channel) + .shouldStore(true) + .sync(); + + pause(3); + + // /v2/history + final PNHistoryResult v2HistoryResult = pubNub.history() + .channel(channel) + .includeMeta(true) + .sync(); + assert v2HistoryResult != null; + assertEquals(1, v2HistoryResult.getMessages().size()); + assertNotNull(v2HistoryResult.getMessages().get(0).getMeta()); + + // /v3/history + final PNFetchMessagesResult v3HistoryResult = pubNub.fetchMessages() + .channels(Collections.singletonList(channel)) + .includeMeta(true) + .sync(); + assert v3HistoryResult != null; + assertEquals(1, v3HistoryResult.getChannels().get(channel).size()); + assertNotNull(v3HistoryResult.getChannels().get(channel).get(0).getMeta()); + + // /v3/history-with-actions + final PNFetchMessagesResult v3HistoryWithActionsResult = pubNub.fetchMessages() + .channels(Collections.singletonList(channel)) + .includeMeta(true) + .includeMessageActions(true) + .sync(); + assert v3HistoryWithActionsResult != null; + assertEquals(1, v3HistoryWithActionsResult.getChannels().get(channel).size()); + assertNotNull(v3HistoryWithActionsResult.getChannels().get(channel).get(0).getMeta()); + + // three responses from three different APIs will return a non-null meta field + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/MessageActionsTest.java b/src/integrationTest/java/com/pubnub/api/integration/MessageActionsTest.java new file mode 100644 index 000000000..eedc0002c --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/MessageActionsTest.java @@ -0,0 +1,716 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.endpoints.message_actions.GetMessageActions; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult; +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult; +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.hamcrest.core.IsEqual; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_TIMETOKEN_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_TYPE_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_VALUE_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_TIMETOKEN_MISSING; +import static com.pubnub.api.integration.util.Utils.isSorted; +import static com.pubnub.api.integration.util.Utils.parseDate; +import static com.pubnub.api.integration.util.Utils.publish; +import static com.pubnub.api.integration.util.Utils.publishMixed; +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class MessageActionsTest extends BaseIntegrationTest { + + private PNPublishResult publishResult; + private static final String expectedChannel = random(); + + @Override + protected void onBefore() { + try { + publishResult = pubNub.publish() + .channel(expectedChannel) + .message(generatePayload()) + .shouldStore(true) + .sync(); + } catch (PubNubException e) { + e.printStackTrace(); + throw new RuntimeException("Message should have been published."); + } + } + + @Test + public void testAddMessageAction() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue("smiley") + .setMessageTimetoken(publishResult.getTimetoken())) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNAddMessageAction, status.getOperation()); + success.set(true); + }); + + listen(success); + } + + @Test + public void testGetMessageAction() throws PubNubException { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType(random()) + .setValue(random()) + .setMessageTimetoken(publishResult.getTimetoken())) + .sync(); + + pubNub.getMessageActions() + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNGetMessageActions, status.getOperation()); + success.set(true); + }); + + listen(success); + } + + @Test + public void testDeleteMessageAction() throws PubNubException { + final AtomicBoolean success = new AtomicBoolean(); + + final String expectedValue = UUID.randomUUID().toString(); + + final PNAddMessageActionResult addMessageActionResult = pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(expectedValue) + .setMessageTimetoken(publishResult.getTimetoken())) + .sync(); + + assert addMessageActionResult != null; + pubNub.removeMessageAction() + .messageTimetoken(publishResult.getTimetoken()) + .actionTimetoken(addMessageActionResult.getActionTimetoken()) + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNDeleteMessageAction, status.getOperation()); + success.set(true); + }); + + listen(success); + } + + @Test + public void testAddGetMessageAction() throws PubNubException { + final AtomicBoolean success = new AtomicBoolean(); + + final String expectedValue = UUID.randomUUID().toString(); + + final PNAddMessageActionResult addMessageActionResult = pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("REACTION") + .setValue(expectedValue) + .setMessageTimetoken(publishResult.getTimetoken())) + .sync(); + + pubNub.getMessageActions() + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNGetMessageActions, status.getOperation()); + assert result != null; + result.getActions().forEach(pnAction -> { + assert addMessageActionResult != null; + if (pnAction.getActionTimetoken().equals(addMessageActionResult.getActionTimetoken())) { + success.set(true); + } + } + ); + }); + + listen(success); + } + + @Test + public void testAddGetMessageAction_Bulk() { + final AtomicBoolean success = new AtomicBoolean(); + + final int expectedMessageCount = 10; + final String expectedChannel = random(); + + publishMixed(pubNub, expectedMessageCount, expectedChannel).forEach(pnPublishResult -> { + try { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(pnPublishResult.getTimetoken())) + .sync(); + } catch (PubNubException e) { + e.printStackTrace(); + } + }); + + pubNub.getMessageActions() + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNGetMessageActions, status.getOperation()); + assert result != null; + assertEquals(expectedMessageCount, result.getActions().size()); + success.set(true); + }); + + listen(success); + } + + @Test + public void testAddGetMessageAction_Bulk_Pagination() throws PubNubException, InterruptedException { + final String expectedChannelName = random(); + + final int messageCount = 10; + + final List messages = publishMixed(pubNub, messageCount, expectedChannelName); + + assertEquals(10, messages.size()); + + for (int i = 0; i < messages.size(); i++) { + pause((int) Durations.ONE_HUNDRED_MILLISECONDS.getSeconds()); + pubNub.addMessageAction() + .channel(expectedChannelName) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue((i + 1) + "_" + RandomGenerator.emoji()) + .setMessageTimetoken(messages.get(i).getTimetoken())) + .sync(); + } + + final AtomicBoolean success = new AtomicBoolean(); + final AtomicInteger count = new AtomicInteger(0); + + page(expectedChannelName, System.currentTimeMillis() * 10_000L, new Callback() { + @Override + public void onMore(List actions) { + count.set(count.get() + actions.size()); + } + + @Override + public void onDone() { + log.error(String.format("onDone %s", count.get())); + success.set(count.get() == messageCount); + } + }); + + listen(success); + } + + void page(String channel, Long start, Callback callback) { + pubNub.getMessageActions() + .channel(channel) + .start(start) + .limit(5) + .async((result, status) -> { + assert result != null; + if (!status.isError() && !result.getActions().isEmpty()) { + callback.onMore(result.getActions()); + page(channel, result.getActions().get(0).getActionTimetoken(), callback); + } else { + callback.onDone(); + } + }); + } + + interface Callback { + void onMore(List actions); + + void onDone(); + } + + @Test + public void loopActions() throws PubNubException { + final List s1 = new ArrayList<>(); + s1.add(1L); + s1.add(2L); + s1.add(3L); + s1.add(4L); + + final List s2 = new ArrayList<>(); + s2.add(3L); + s2.add(4L); + s2.add(1L); + s2.add(2L); + + final List s3 = new ArrayList<>(); + s3.add(4L); + s3.add(3L); + s3.add(2L); + s3.add(1L); + + assertTrue(isSorted(s1)); + assertFalse(isSorted(s2)); + assertTrue(isSorted(s3)); + + final List publishList = new ArrayList<>(); + + for (int i = 0; i < 11; i++) { + publishList.add(publish(pubNub, expectedChannel, i + 1)); + } + + for (int i = 0; i < publishList.size(); i++) { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("REACTION") + .setValue((i + 1) + "_" + RandomGenerator.newValue(5)) + .setMessageTimetoken(publishList.get(i).getTimetoken())) + .sync(); + } + + final int[] size = {0}; + + final List pnActionList = new ArrayList<>(); + + pageActions(3, expectedChannel, null, new Callback() { + @Override + public void onMore(List actions) { + size[0] += actions.size(); + System.out.println("Moreee " + actions.size() + "/" + size[0]); + + final List temp = new ArrayList<>(actions); + actions.forEach(pnAction -> { + System.out.print(parseDate(pnAction.getActionTimetoken())); + System.out.println(" – " + pnAction.getValue()); + }); + + Collections.reverse(temp); + + pnActionList.addAll(temp); + + + } + + @Override + public void onDone() { + System.out.println("Done!"); + + pnActionList.forEach(pnAction -> { + System.out.print(parseDate(pnAction.getActionTimetoken())); + System.out.println(" – " + pnAction.getValue()); + }); + + final List tts = new ArrayList<>(); + pnActionList.forEach(pnAction -> tts.add(pnAction.getActionTimetoken())); + + assertTrue(isSorted(tts)); + } + }); + } + + public void pageActions(int chunk, String channel, Long start, Callback callback) throws PubNubException { + final GetMessageActions builder = pubNub.getMessageActions() + .limit(chunk) + .channel(channel); + + if (start != null) { + builder.start(start); + } + + final PNGetMessageActionsResult messageActionsResult = builder.sync(); + + assert messageActionsResult != null; + if (!messageActionsResult.getActions().isEmpty()) { + callback.onMore(messageActionsResult.getActions()); + pageActions(chunk, channel, messageActionsResult.getActions().get(0).getActionTimetoken(), callback); + } else { + callback.onDone(); + } + } + + + @Test + public void testFetchHistory() throws PubNubException { + final String expectedChannel = random(); + final int expectedMessageCount = 10; + + final List publishResultList = publishMixed(pubNub, expectedMessageCount, expectedChannel); + + for (int i = 0; i < publishResultList.size(); i++) { + if (i % 2 == 0 && i % 3 == 0) { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("receipt") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(publishResultList.get(i).getTimetoken())) + .sync(); + } + if (i % 3 == 0) { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("receipt") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(publishResultList.get(i).getTimetoken())) + .sync(); + } + if (i % 2 == 0) { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("receipt") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(publishResultList.get(i).getTimetoken())) + .sync(); + } + if (i % 5 == 0) { + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("fiver") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(publishResultList.get(i).getTimetoken())) + .sync(); + } + } + + final PNFetchMessagesResult fetchMessagesResultWithActions = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .includeMeta(true) + .includeMessageActions(true) + .sync(); + + assert fetchMessagesResultWithActions != null; + fetchMessagesResultWithActions.getChannels().forEach((channel, item) -> { + System.out.println("Channel: " + channel + ". Messages: " + item.size()); + item.forEach(pnFetchMessageItem -> { + System.out.println("\tMessage: " + pnFetchMessageItem.getMessage()); + System.out.println("\tTimetoken: " + pnFetchMessageItem.getTimetoken()); + System.out.println("\tMeta: " + pnFetchMessageItem.getMeta()); + if (pnFetchMessageItem.getActions() == null) { + System.out.println("\t\tNo actions here."); + return; + } + System.out.println("\t\tTotal action types: " + pnFetchMessageItem.getActions().size()); + pnFetchMessageItem.getActions().forEach((type, map) -> { + System.out.println("\t\t\tAction type: " + type); + map.forEach((value, actions) -> { + System.out.println("\t\t\t\tAction value: " + value); + actions.forEach(action -> { + System.out.println("\t\t\t\tAction uuid: " + action.getUuid()); + System.out.println("\t\t\t\tAction timetoken: " + action.getActionTimetoken()); + }); + }); + }); + System.out.println("--------------------"); + }); + }); + + fetchMessagesResultWithActions.getChannels().forEach((s, pnFetchMessageItems) -> { + pnFetchMessageItems.forEach(pnFetchMessageItem -> { + assertNotNull(pnFetchMessageItem.getActions()); + }); + }); + + final PNFetchMessagesResult fetchMessagesResultNoActions = pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .sync(); + + assert fetchMessagesResultNoActions != null; + fetchMessagesResultNoActions.getChannels().forEach((s, pnFetchMessageItems) -> { + pnFetchMessageItems.forEach(pnFetchMessageItem -> { + assertNull(pnFetchMessageItem.getActions()); + }); + }); + } + + @Test + public void testActionReceive() throws PubNubException { + final String expectedChannelName = randomChannel(); + + final int expectedMessageCount = 2; + + final List publishResultList = new ArrayList<>(); + + for (int i = 1; i <= expectedMessageCount; i++) { + final PNPublishResult pnPublishResult = pubNub.publish() + .channel(expectedChannelName) + .message(i + "_msg") + .meta(i % 2 == 0 ? generateMap() : null) + .shouldStore(true) + .sync(); + publishResultList.add(pnPublishResult); + } + + assertEquals(expectedMessageCount, publishResultList.size()); + + final AtomicInteger actionsCount = new AtomicInteger(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + if (pnStatus.getCategory() == PNStatusCategory.PNConnectedCategory) { + if (pnStatus.getOperation() == PNOperationType.PNSubscribeOperation) { + for (PNPublishResult pnPublishResult : publishResultList) { + try { + pubNub.addMessageAction() + .channel(expectedChannelName) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(pnPublishResult.getTimetoken())) + .sync(); + } catch (PubNubException e) { + e.printStackTrace(); + } + } + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + fail(); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + fail(); + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + fail(); + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + fail(); + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + fail(); + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + assertEquals(expectedChannelName, pnActionResult.getChannel()); + actionsCount.incrementAndGet(); + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(expectedChannelName)) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(Durations.TEN_SECONDS) + .untilAtomic(actionsCount, IsEqual.equalTo(expectedMessageCount)); + + + } + + @Test + public void testAddAction_NoChannel() { + try { + pubNub.addMessageAction() + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_CHANNEL_MISSING, e); + } + } + + @Test + public void testAddAction_NoMessageActionObject() { + try { + pubNub.addMessageAction() + .channel(random()) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_ACTION_MISSING, e); + } + } + + @Test + public void testAddAction_NoMessageTimeToken() { + try { + pubNub.addMessageAction() + .channel(random()) + .messageAction(new PNMessageAction() + .setType(random()) + .setValue(random())) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_TIMETOKEN_MISSING, e); + } + } + + @Test + public void testAddAction_NoMessageActionType() { + try { + pubNub.addMessageAction() + .channel(random()) + .messageAction(new PNMessageAction() + .setValue(random()) + .setMessageTimetoken(1L) + ) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_ACTION_TYPE_MISSING, e); + } + } + + @Test + public void testAddAction_NoMessageActionValue() { + try { + pubNub.addMessageAction() + .channel(random()) + .messageAction(new PNMessageAction() + .setType(random()) + .setMessageTimetoken(1L) + ) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_ACTION_VALUE_MISSING, e); + } + } + + @Test + public void testGetActions_NoChannel() { + try { + pubNub.getMessageActions() + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_CHANNEL_MISSING, e); + } + } + + @Test + public void testRemoveAction_NoChannel() { + try { + pubNub.removeMessageAction() + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_CHANNEL_MISSING, e); + } + } + + @Test + public void testRemoveAction_NoMessageTimeToken() { + try { + pubNub.removeMessageAction() + .channel(random()) + .actionTimetoken(1L) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_TIMETOKEN_MISSING, e); + } + } + + @Test + public void testRemoveAction_NoMessageActionTimeToken() { + try { + pubNub.removeMessageAction() + .channel(random()) + .messageTimetoken(1L) + .sync(); + } catch (PubNubException e) { + assertException(PNERROBJ_MESSAGE_ACTION_TIMETOKEN_MISSING, e); + } + } + + @Test + public void testAddSameActionTwice() throws PubNubException { + final String expectedChannel = random(); + final String expectedEmoji = RandomGenerator.emoji(); + + final PNPublishResult pnPublishResult = pubNub.publish() + .channel(expectedChannel) + .message(random()) + .shouldStore(true) + .sync(); + + assert pnPublishResult != null; + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(expectedEmoji) + .setMessageTimetoken(pnPublishResult.getTimetoken()) + ) + .sync(); + + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(expectedEmoji) + .setMessageTimetoken(pnPublishResult.getTimetoken()) + ) + .async((result, status) -> { + assertTrue(status.isError()); + assertEquals(409, status.getStatusCode()); + success.set(true); + + }); + + listen(success); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/PAMFilesIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/PAMFilesIntegrationTests.java new file mode 100644 index 000000000..e009580ad --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/PAMFilesIntegrationTests.java @@ -0,0 +1,80 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.ITTestConfig; +import com.pubnub.api.models.consumer.files.PNDownloadFileResult; +import com.pubnub.api.models.consumer.files.PNFileUploadResult; +import org.aeonbits.owner.ConfigFactory; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNotNull; + +public class PAMFilesIntegrationTests extends BaseIntegrationTest { + //See README.md in integrationTest directory for more info on running integration tests + private static ITTestConfig IT_TEST_CONFIG = ConfigFactory.create(ITTestConfig.class, System.getenv()); + + private static final String FILENAME = "file.txt"; + + private static final String CHANNEL = "chan-" + randomId(); + private static final String CLIENT_UUID = "client-" + randomId(); + private static final String CLIENT_AUTH_KEY = randomId(); + + private static final String FILE_CONTENT = "some string"; + + @Test + public void canSendAndDownloadFileWithPAM() throws PubNubException, IOException { + assumeNotNull(getServer().getConfiguration().getSecretKey()); + + final PubNub adminPubnub = getServer(); + final PubNub pubnub = getPubNub(); + + try { + adminPubnub.grant() + .authKeys(singletonList(CLIENT_AUTH_KEY)) + .channels(singletonList(CHANNEL)) + .read(true) + .write(true) + .join(false) + .manage(false) + .get(false) + .update(false) + .delete(false) + .ttl(0) + .sync(); + + final byte[] fileContentBytes = FILE_CONTENT.getBytes(StandardCharsets.UTF_8); + + final PNFileUploadResult pnFileUploadResult = pubnub.sendFile() + .channel(CHANNEL) + .fileName(FILENAME) + .inputStream(new ByteArrayInputStream(fileContentBytes)) + .sync(); + + final PNDownloadFileResult pnDownloadFileResult = pubnub.downloadFile().channel(CHANNEL) + .fileName(pnFileUploadResult.getFile().getName()) + .fileId(pnFileUploadResult.getFile().getId()) + .sync(); + + final String retrievedContent = IOUtils.toString(pnDownloadFileResult.getByteStream()); + assertEquals(FILE_CONTENT, retrievedContent); + } + finally { + adminPubnub.forceDestroy(); + pubnub.forceDestroy(); + } + } + + private static String randomId() { + return UUID.randomUUID().toString(); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/PresenceEventsIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/PresenceEventsIntegrationTests.java new file mode 100644 index 000000000..9fa032a87 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/PresenceEventsIntegrationTests.java @@ -0,0 +1,294 @@ +package com.pubnub.api.integration; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; + +public class PresenceEventsIntegrationTests extends BaseIntegrationTest { + + @Test + public void testJoinChannel() { + final String channel = randomChannel(); + final AtomicBoolean success = new AtomicBoolean(false); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("join")) { + assertEquals(channel, presence.getChannel()); + pubNub.removeListener(this); + success.set(true); + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + subscribeToChannel(pubNub, channel); + + listen(success); + } + + @Test + @Ignore + public void testLeaveChannel() { + final AtomicBoolean success = new AtomicBoolean(false); + final String channel = randomChannel(); + + final PubNub guestClient = getPubNub(); + + this.pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("leave")) { + assertEquals(channel, presence.getChannel()); + success.set(true); + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + listen(success, () -> { + subscribeToChannel(pubNub, channel); + subscribeToChannel(guestClient, channel); + unsubscribeFromChannel(guestClient, channel); + return success.get(); + }); + } + + @Test + public void testTimeoutFromChannel() { + final AtomicBoolean success = new AtomicBoolean(false); + pubNub.getConfiguration().setPresenceTimeoutWithCustomInterval(20, 0); + + assertEquals(20, pubNub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubNub.getConfiguration().getHeartbeatInterval()); + + final String channel = random(); + final int waitTime = 21; + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("timeout")) { + assertEquals(channel, presence.getChannel()); + success.set(true); + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + subscribeToChannel(pubNub, channel); + + Awaitility.await() + .atMost(waitTime + 1, TimeUnit.SECONDS) + .with() + .pollDelay(waitTime, TimeUnit.SECONDS) + .untilTrue(success); + } + + @Test + public void testStateChangeEvent() { + final AtomicBoolean success = new AtomicBoolean(false); + + final JsonObject state = generatePayload(); + final String channel = randomChannel(); + + subscribeToChannel(pubNub, channel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("state-change") && presence.getUuid() + .equals(pubNub.getConfiguration().getUuid())) { + assertEquals("state-change", presence.getEvent()); + pubNub.removeListener(this); + success.set(true); + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + pubNub.setPresenceState() + .channels(Collections.singletonList(channel)) + .state(state) + .async((result, status) -> { + + }); + + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(success); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/PresenceIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/PresenceIntegrationTests.java new file mode 100644 index 000000000..acdcca17e --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/PresenceIntegrationTests.java @@ -0,0 +1,458 @@ +package com.pubnub.api.integration; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNHeartbeatNotificationOptions; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.presence.PNHereNowChannelData; +import com.pubnub.api.models.consumer.presence.PNHereNowOccupantData; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.hamcrest.core.IsEqual; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PresenceIntegrationTests extends BaseIntegrationTest { + + @Test + public void testWhereNow() { + final AtomicBoolean success = new AtomicBoolean(); + + final int expectedChannelsCount = 4; + + final List expectedChannels = new ArrayList<>(); + for (int i = 0; i < expectedChannelsCount; i++) { + expectedChannels.add(RandomGenerator.get()); + } + + subscribeToChannel(pubNub, expectedChannels); + + pause(TIMEOUT_MEDIUM); + + pubNub.whereNow() + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(expectedChannelsCount, result.getChannels().size()); + for (String channel : result.getChannels()) { + assertTrue(expectedChannels.contains(channel)); + } + success.set(true); + }); + + listen(success); + } + + @Test + public void testGlobalHereNow() { + final AtomicBoolean success = new AtomicBoolean(); + + final int expectedChannelsCount = 2; + final int expectedClientsCount = 3; + + final List expectedChannels = new ArrayList<>(expectedChannelsCount); + for (int i = 0; i < expectedChannelsCount; i++) { + expectedChannels.add(RandomGenerator.get()); + } + + final List clients = new ArrayList(expectedClientsCount) {{ + add(pubNub); + }}; + + for (int i = 1; i <= expectedClientsCount - 1; i++) { + clients.add(getPubNub()); + } + + for (PubNub client : clients) { + subscribeToChannel(client, expectedChannels); + } + + assertEquals(expectedClientsCount, clients.size()); + + for (PubNub client : clients) { + assertEquals(expectedChannelsCount, client.getSubscribedChannels().size()); + } + + pause(TIMEOUT_MEDIUM); + + pubNub.hereNow() + .includeUUIDs(true) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertTrue(result.getTotalOccupancy() >= expectedClientsCount); + assertTrue(result.getTotalChannels() >= expectedChannelsCount); + assertTrue(result.getChannels().size() >= expectedChannelsCount); + + final List channelsResult = new ArrayList() {{ + addAll(result.getChannels().keySet()); + }}; + + assertTrue(channelsResult.containsAll(expectedChannels)); + + for (Map.Entry entry : result.getChannels().entrySet()) { + if (expectedChannels.contains(entry.getKey())) { + assertTrue(entry.getValue().getOccupancy() >= expectedClientsCount); + assertTrue(entry.getValue().getOccupants().size() >= expectedClientsCount); + final List occupants = entry.getValue().getOccupants(); + + final List resultUuidList = new ArrayList<>(); + for (PNHereNowOccupantData occupant : occupants) { + resultUuidList.add(occupant.getUuid()); + } + + final List expectedUuidList = new ArrayList<>(); + for (PubNub client : clients) { + expectedUuidList.add(client.getConfiguration().getUuid()); + } + + Collections.sort(expectedUuidList); + Collections.sort(resultUuidList); + + assertEquals(expectedUuidList, resultUuidList); + } + + } + + success.set(true); + }); + + listen(success); + } + + @Test + public void testHereNow() { + final AtomicBoolean success = new AtomicBoolean(); + + final int expectedChannelsCount = 3; + final int expectedClientsCount = 4; + + final List expectedChannels = new ArrayList<>(expectedChannelsCount); + for (int i = 0; i < expectedChannelsCount; i++) { + expectedChannels.add(RandomGenerator.get()); + } + + final List clients = new ArrayList() {{ + add(pubNub); + }}; + for (int i = 1; i < expectedClientsCount; i++) { + clients.add(getPubNub()); + } + + for (PubNub client : clients) { + subscribeToChannel(client, expectedChannels); + } + + assertEquals(expectedChannelsCount, expectedChannels.size()); + assertEquals(expectedClientsCount, clients.size()); + + pause(TIMEOUT_MEDIUM); + + pubNub.hereNow() + .channels(expectedChannels) + .includeUUIDs(true) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(expectedChannelsCount, result.getTotalChannels()); + assertEquals(expectedChannelsCount, result.getChannels().size()); + assertEquals(expectedChannelsCount * expectedClientsCount, result.getTotalOccupancy()); + + for (Map.Entry entry : result.getChannels().entrySet()) { + assertTrue(expectedChannels.contains(entry.getKey())); + assertTrue(expectedChannels.contains(entry.getValue().getChannelName())); + assertEquals(expectedClientsCount, entry.getValue().getOccupancy()); + assertEquals(expectedClientsCount, entry.getValue().getOccupants().size()); + for (PNHereNowOccupantData occupant : entry.getValue().getOccupants()) { + final String uuid = occupant.getUuid(); + boolean contains = false; + for (PubNub client : clients) { + if (client.getConfiguration().getUuid().equals(uuid)) { + contains = true; + break; + } + } + assertTrue(contains); + } + } + + success.set(true); + }); + + listen(success); + } + + @Test + public void testPresenceState() { + final AtomicInteger hits = new AtomicInteger(); + final int expectedHits = 2; + + final JsonObject expectedStatePayload = generatePayload(); + final String expectedChannel = RandomGenerator.get(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("state-change") + && presence.getChannel().equals(expectedChannel) + && presence.getUuid().equals(pubNub.getConfiguration().getUuid())) { + assertEquals(expectedStatePayload, presence.getState()); + hits.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + subscribeToChannel(pubNub, expectedChannel); + + pubNub.setPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .state(expectedStatePayload) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(expectedStatePayload, result.getState()); + }); + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilAtomic(hits, IsEqual.equalTo(1)); + + pubNub.getPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(expectedStatePayload, result.getStateByUUID().get(expectedChannel)); + hits.incrementAndGet(); + }); + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilAtomic(hits, IsEqual.equalTo(expectedHits)); + + } + + @Test + public void testHeartbeatsDisabled() { + final AtomicInteger heartbeatCallsCount = new AtomicInteger(0); + final AtomicBoolean subscribeSuccess = new AtomicBoolean(); + final String expectedChannel = RandomGenerator.get(); + + pubNub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubNub.getConfiguration().getHeartbeatNotificationOptions()); + assertEquals(300, pubNub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubNub.getConfiguration().getHeartbeatInterval()); + + pubNub.getConfiguration().setPresenceTimeoutWithCustomInterval(20, 0); + assertEquals(20, pubNub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubNub.getConfiguration().getHeartbeatInterval()); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (!status.isError()) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().contains(expectedChannel)) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true); + } + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + heartbeatCallsCount.incrementAndGet(); + } + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(expectedChannel)) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(22, TimeUnit.SECONDS) + .pollDelay(21, TimeUnit.SECONDS) + .until(() -> subscribeSuccess.get() && heartbeatCallsCount.get() == 0); + } + + @Test + public void testHeartbeatsEnabled() { + final AtomicInteger heartbeatCallsCount = new AtomicInteger(0); + final AtomicBoolean subscribeSuccess = new AtomicBoolean(); + final String expectedChannel = RandomGenerator.get(); + + pubNub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubNub.getConfiguration().getHeartbeatNotificationOptions()); + assertEquals(300, pubNub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubNub.getConfiguration().getHeartbeatInterval()); + + pubNub.getConfiguration().setPresenceTimeout(20); + assertEquals(20, pubNub.getConfiguration().getPresenceTimeout()); + assertEquals(9, pubNub.getConfiguration().getHeartbeatInterval()); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (!status.isError()) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().contains(expectedChannel)) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true); + } + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + heartbeatCallsCount.incrementAndGet(); + } + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(expectedChannel)) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(20, TimeUnit.SECONDS) + .until(() -> subscribeSuccess.get() && heartbeatCallsCount.get() > 2); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java new file mode 100644 index 000000000..0e8e245e1 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/PublishIntegrationTests.java @@ -0,0 +1,733 @@ +package com.pubnub.api.integration; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult; +import com.pubnub.api.models.consumer.history.PNHistoryResult; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.hamcrest.core.IsEqual; +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class PublishIntegrationTests extends BaseIntegrationTest { + + @Override + protected void onBefore() { + + } + + @Override + protected void onAfter() { + + } + + @Test + public void testPublishMessage() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = randomChannel(); + final JsonObject messagePayload = generateMessage(pubNub); + + pubNub.publish() + .message(messagePayload) + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(status.getUuid(), pubNub.getConfiguration().getUuid()); + success.set(true); + }); + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilTrue(success); + } + + @Test + public void testPublishMessageHistory() throws PubNubException { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = randomChannel(); + final JSONObject messagePayload = new JSONObject(); + + try { + messagePayload.put("name", "joe"); + messagePayload.put("age", 48); + } catch (JSONException e) { + e.printStackTrace(); + } + + final JsonObject whatToExpect = pubNub.getMapper().convertValue(messagePayload, JsonObject.class); + + pubNub.publish() + .channel(expectedChannel) + .message(messagePayload) + .sync(); + + pause(2); + + pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .maximumPerChannel(1) + .async((result, status) -> { + assertFalse(status.isError()); + assert result != null; + assertEquals(1, result.getChannels().size()); + assertEquals(1, result.getChannels().get(expectedChannel).size()); + assertEquals(whatToExpect, result.getChannels().get(expectedChannel).get( + 0).getMessage()); + success.set(true); + }); + + Awaitility.await().atMost(Durations.TEN_SECONDS).untilTrue(success); + } + + @Test + public void testPublishMessageNoHistory() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = randomChannel(); + final JsonObject messagePayload = generateMessage(pubNub); + + pubNub.publish() + .message(messagePayload) + .channel(expectedChannel) + .shouldStore(false) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(status.getUuid(), pubNub.getConfiguration().getUuid()); + }); + + pause(2); + + pubNub.history() + .count(1) + .channel(expectedChannel) + .async((pnHistoryResult, pnStatus) -> { + assertFalse(pnStatus.isError()); + assert pnHistoryResult != null; + assertEquals(0, pnHistoryResult.getMessages().size()); + success.set(true); + }); + + Awaitility.await().atMost(Durations.TEN_SECONDS).untilTrue(success); + } + + @Test + public void testReceiveMessage() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = randomChannel(); + final JsonObject messagePayload = generateMessage(pubNub); + + final PubNub observer = getPubNub(); + + this.pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().contains(expectedChannel)) { + observer.publish() + .message(messagePayload) + .channel(expectedChannel) + .async((result, status1) -> assertFalse(status1.isError())); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + assertEquals(expectedChannel, message.getChannel()); + assertEquals(observer.getConfiguration().getUuid(), message.getPublisher()); + assertEquals(messagePayload, message.getMessage()); + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + subscribeToChannel(pubNub, expectedChannel); + + Awaitility.await().atMost(Durations.TEN_SECONDS).untilTrue(success); + } + + @Test + public void testOrgJsonObject_Get_History() throws PubNubException, JSONException { + final String channel = random(); + + final JSONObject payload = new JSONObject(); + payload.put("name", "John Doe"); + payload.put("city", "San Francisco"); + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + pause(3); + + final PNHistoryResult historyResult = pubNub.history() + .channel(channel) + .count(1) + .sync(); + + assert historyResult != null; + final JsonElement receivedMessage = historyResult.getMessages().get(0).getEntry(); + + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + + assertEquals(payload.toString(), receivedObject.toString()); + } + + @Test + public void testOrgJsonObject_Post_History() throws PubNubException, JSONException { + final String channel = random(); + + final JSONObject payload = generatePayloadJSON(); + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + pause(3); + + final PNHistoryResult historyResult = pubNub.history() + .channel(channel) + .count(1) + .sync(); + assert historyResult != null; + final JsonElement receivedMessage = historyResult.getMessages().get(0).getEntry(); + + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + + assertEquals(payload.toString(), receivedObject.toString()); + } + + @Test + public void testOrgJsonObject_Get_Receive() throws PubNubException { + final String channel = random(); + + final JSONObject payload = generatePayloadJSON(); + + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + final JsonElement receivedMessage = pnMessageResult.getMessage(); + try { + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + assertEquals(payload.toString(), receivedObject.toString()); + success.set(true); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + pause(3); + + pubNub.publish() + .message(payload) + .channel(channel) + .sync(); + + listen(success); + } + + @Test + public void testOrgJsonObject_Post_Receive() throws PubNubException { + final String channel = random(); + final JSONObject payload = generatePayloadJSON(); + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + final JsonElement receivedMessage = pnMessageResult.getMessage(); + try { + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + assertEquals(payload.toString(), receivedObject.toString()); + success.set(true); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + pause(3); + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + listen(success); + } + + @Test + public void testOrgJsonArray_Get_History() throws PubNubException, JSONException { + final String channel = random(); + + final JSONArray payload = new JSONArray(); + for (int i = 0; i < 3; i++) { + payload.put(generatePayloadJSON()); + } + + pubNub.publish() + .message(payload) + .channel(channel) + .sync(); + + pause(3); + + final PNHistoryResult historyResult = pubNub.history() + .channel(channel) + .count(1) + .sync(); + + assert historyResult != null; + final JsonElement receivedMessage = historyResult.getMessages().get(0).getEntry(); + + final JSONArray receivedArray = new JSONArray(receivedMessage.toString()); + + assertEquals(payload.toString(), receivedArray.toString()); + } + + @Test + public void testOrgJsonArray_Post_History() throws PubNubException, JSONException { + final String channel = random(); + + final JSONArray payload = new JSONArray(); + for (int i = 0; i < 3; i++) { + payload.put(generatePayloadJSON()); + } + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + pause(3); + + final PNHistoryResult historyResult = pubNub.history() + .channel(channel) + .count(1) + .sync(); + assert historyResult != null; + final JsonElement receivedMessage = historyResult.getMessages().get(0).getEntry(); + + final JSONArray receivedArray = new JSONArray(receivedMessage.toString()); + + assertEquals(payload.toString(), receivedArray.toString()); + } + + @Test + public void testOrgJsonArray_Get_Receive() throws PubNubException { + final String channel = random(); + + final JSONArray payload = new JSONArray(); + for (int i = 0; i < 3; i++) { + payload.put(generatePayloadJSON()); + } + + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + final JsonElement receivedMessage = pnMessageResult.getMessage(); + try { + final JSONArray receivedArray = new JSONArray(receivedMessage.toString()); + assertEquals(payload.toString(), receivedArray.toString()); + success.set(true); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + pause(3); + + pubNub.publish() + .message(payload) + .channel(channel) + .sync(); + + listen(success); + } + + @Test + public void testOrgJsonArray_Post_Receive() throws PubNubException { + final String channel = random(); + + final JSONArray payload = new JSONArray(); + for (int i = 0; i < 3; i++) { + payload.put(generatePayloadJSON()); + } + + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + final JsonElement receivedMessage = pnMessageResult.getMessage(); + try { + final JSONArray receivedArray = new JSONArray(receivedMessage.toString()); + assertEquals(payload.toString(), receivedArray.toString()); + success.set(true); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + pause(3); + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + listen(success); + } + + @Test + public void testOrgJson_Combo() throws PubNubException, JSONException { + final String channel = random(); + + final JSONObject payload = new JSONObject(); + payload.put("key_1", generatePayloadJSON()); + payload.put("key_2", generatePayloadJSON()); + payload.put("z_1", new JSONObject(payload.toString())); + payload.put("a_2", new JSONObject(payload.toString())); + payload.put("d_3", new JSONObject(payload.toString())); + + final JSONArray jsonArray = new JSONArray(); + jsonArray.put(generatePayloadJSON()); + jsonArray.put(generatePayloadJSON()); + jsonArray.put(generatePayloadJSON()); + + payload.put("z_array", jsonArray); + + final AtomicInteger count = new AtomicInteger(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + final JsonElement receivedMessage = pnMessageResult.getMessage(); + try { + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + assertEquals(payload.toString(), receivedObject.toString()); + count.incrementAndGet(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + pause(3); + + pubNub.publish() + .message(payload) + .channel(channel) + .usePOST(true) + .sync(); + + pause(3); + + final PNFetchMessagesResult historyResult = pubNub.fetchMessages() + .channels(Collections.singletonList(channel)) + .maximumPerChannel(1) + .sync(); + + assert historyResult != null; + final JsonElement receivedMessage = historyResult.getChannels().get(channel).get(0).getMessage(); + + final JSONObject receivedObject = new JSONObject(receivedMessage.toString()); + assertEquals(payload.toString(), receivedObject.toString()); + count.incrementAndGet(); + + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .untilAtomic(count, IsEqual.equalTo(2)); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/PushIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/PushIntegrationTest.java new file mode 100644 index 000000000..ac10ea906 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/PushIntegrationTest.java @@ -0,0 +1,115 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult; +import org.junit.Test; + +import java.util.*; + +import static com.pubnub.api.integration.util.RandomGenerator.randomNumber; +import static org.junit.Assert.*; + +public class PushIntegrationTest extends BaseIntegrationTest { + + private final List expectedChannels = new ArrayList<>(); + private String expectedDeviceId; + private String expectedTopic; + + @Override + protected void onBefore() { + for (int i = 0; i < 3; i++) { + expectedChannels.add(UUID.randomUUID().toString().substring(0, 8).toUpperCase(Locale.US)); + } + + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 70; i++) { + builder.append(randomNumber(0, 9)); + } + expectedDeviceId = builder.toString(); + + expectedTopic = UUID.randomUUID().toString(); + } + + @Test + public void testEnumNames() { + assertEquals("apns", PNPushType.APNS.toString()); + assertEquals("gcm", PNPushType.GCM.toString()); + assertEquals("gcm", PNPushType.FCM.toString()); + assertEquals("mpns", PNPushType.MPNS.toString()); + assertEquals("apns2", PNPushType.APNS2.toString()); + } + + @Test + public void testCycle() throws PubNubException, InterruptedException { + for (PNPushType value : PNPushType.values()) { + performCompleteCycle(value); + } + } + + private void performCompleteCycle(PNPushType pushType) throws InterruptedException, PubNubException { + pubNub.addPushNotificationsOnChannels() + .channels(expectedChannels) + .pushType(pushType) + .topic(expectedTopic) + .deviceId(expectedDeviceId) + .sync(); + + Thread.sleep(1000); + + final PNPushListProvisionsResult result = pubNub.auditPushChannelProvisions() + .deviceId(expectedDeviceId) + .pushType(pushType) + .topic(expectedTopic) + .environment(PNPushEnvironment.DEVELOPMENT) + .sync(); + assert result != null; + assertTrue(result.getChannels().containsAll(expectedChannels)); + + Thread.sleep(1000); + + pubNub.removePushNotificationsFromChannels() + .pushType(pushType) + .environment(PNPushEnvironment.DEVELOPMENT) + .deviceId(expectedDeviceId) + .topic(expectedTopic) + .channels(Collections.singletonList(expectedChannels.get(0))) + .sync(); + + Thread.sleep(1000); + + final PNPushListProvisionsResult oneRemoved = pubNub.auditPushChannelProvisions() + .deviceId(expectedDeviceId) + .pushType(pushType) + .topic(expectedTopic) + .environment(PNPushEnvironment.DEVELOPMENT) + .sync(); + assert oneRemoved != null; + assertFalse(oneRemoved.getChannels().isEmpty()); + assertFalse(oneRemoved.getChannels().contains(expectedChannels.get(0))); + + Thread.sleep(1000); + + pubNub.removeAllPushNotificationsFromDeviceWithPushToken() + .pushType(pushType) + .environment(PNPushEnvironment.DEVELOPMENT) + .deviceId(expectedDeviceId) + .topic(expectedTopic) + .sync(); + + Thread.sleep(1000); + + final PNPushListProvisionsResult listResult = pubNub.auditPushChannelProvisions() + .deviceId(expectedDeviceId) + .pushType(pushType) + .topic(expectedTopic) + .environment(PNPushEnvironment.DEVELOPMENT) + .sync(); + + assert listResult != null; + assertTrue(listResult.getChannels().isEmpty()); + assertFalse(listResult.getChannels().containsAll(expectedChannels)); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/SignalIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/SignalIntegrationTests.java new file mode 100644 index 000000000..3cf53e0cc --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/SignalIntegrationTests.java @@ -0,0 +1,195 @@ +package com.pubnub.api.integration; + +import com.google.gson.Gson; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class SignalIntegrationTests extends BaseIntegrationTest { + + @Override + protected void onBefore() { + } + + @Override + protected void onAfter() { + + } + + @Test + public void testPublishSignalMessageAsync() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = randomChannel(); + final String expectedPayload = RandomGenerator.newValue(5); + + pubNub.signal() + .message(expectedPayload) + .channel(expectedChannel) + .async((result, status) -> { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNSignalOperation, status.getOperation()); + assertEquals(status.getUuid(), pubNub.getConfiguration().getUuid()); + assertNotNull(result); + success.set(true); + }); + + listen(success); + } + + @Test + public void testPublishSignalMessageSync() throws PubNubException { + final String expectedChannel = randomChannel(); + final String expectedPayload = RandomGenerator.newValue(5); + + final PNPublishResult signalResult = pubNub.signal() + .message(expectedPayload) + .channel(expectedChannel) + .sync(); + + assertNotNull(signalResult); + } + + @Test + public void testReceiveSignalMessage() { + final AtomicBoolean success = new AtomicBoolean(); + + final String expectedChannel = randomChannel(); + final String expectedPayload = RandomGenerator.newValue(5); + + final PubNub observerClient = getPubNub(); + + observerClient.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().contains(expectedChannel)) { + pubNub.signal() + .message(expectedPayload) + .channel(expectedChannel) + .async((result, status1) -> { + assertFalse(status1.isError()); + assertEquals(PNOperationType.PNSignalOperation, status1.getOperation()); + assertEquals(status1.getUuid(), pubNub.getConfiguration().getUuid()); + assertNotNull(result); + }); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + assertEquals(pubNub.getConfiguration().getUuid(), signal.getPublisher()); + assertEquals(expectedChannel, signal.getChannel()); + assertEquals(expectedPayload, new Gson().fromJson(signal.getMessage(), String.class)); + success.set(true); + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + observerClient.subscribe() + .channels(Collections.singletonList(expectedChannel)) + .execute(); + + Awaitility.await().atMost(Durations.FIVE_SECONDS).untilTrue(success); + } + + @Test + public void testPublishSignalMessageSyncWithoutChannel() { + try { + pubNub.signal() + .message(randomChannel()) + .sync(); + } catch (PubNubException e) { + assertEquals(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING.getMessage(), e.getPubnubError() + .getMessage()); + } + } + + @Test + public void testPublishSignalMessageSyncWithoutMessage() { + try { + pubNub.signal() + .channel(randomChannel()) + .sync(); + } catch (PubNubException e) { + assertEquals(PubNubErrorBuilder.PNERROBJ_MESSAGE_MISSING.getMessage(), e.getPubnubError() + .getMessage()); + } + } + + @Test + public void testPublishSignalMessageSyncWithoutSubKey() { + try { + pubNub.getConfiguration().setSubscribeKey(null); + + pubNub.signal() + .channel(randomChannel()) + .message(random()) + .sync(); + } catch (PubNubException e) { + assertEquals(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING.getMessage(), e.getPubnubError() + .getMessage()); + } + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.java new file mode 100644 index 000000000..18c20af76 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/StorageAndPlaybackIntegrationTests.java @@ -0,0 +1,167 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import org.junit.Test; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.*; + +public class StorageAndPlaybackIntegrationTests extends BaseIntegrationTest { + + @Override + protected void onBefore() { + + } + + @Override + protected void onAfter() { + + } + + @Test + public void testHistoryMessages() { + final AtomicBoolean success = new AtomicBoolean(); + final String messageText = RandomGenerator.newValue(10); + final String channel = RandomGenerator.newValue(10); + + subscribeToChannel(pubNub, channel); + publishMessage(pubNub, channel, messageText); + pause(1); + + pubNub.history() + .channel(channel) + .async((result, status) -> { + assertNotNull(result); + final String message = result.getMessages().get(0).getEntry().toString(); + assertFalse(status.isError()); + assertTrue(message.contains(pubNub.getConfiguration().getUuid())); + assertTrue(message.contains(messageText)); + success.set(true); + }); + + success.set(true); + } + + @Test + public void testHistoryMessagesWithTimeToken() { + final AtomicBoolean success = new AtomicBoolean(); + final String channel = RandomGenerator.newValue(10); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .execute(); + + publishMessages(channel, 3); + pause(1); + + pubNub.history() + .channel(channel) + .includeTimetoken(true) + .async((result, status) -> { + assertFalse(status.isError()); + assertNotNull(result); + int timeTokenCounter = 0; + for (int i = 0; i < result.getMessages().size(); i++) { + if (result.getMessages().get(i).getTimetoken() != null) { + timeTokenCounter++; + } + } + + assertEquals(3, timeTokenCounter); + success.set(true); + }); + + success.set(true); + } + + @Test + public void testLoadingHistoryMessagesWithLimit() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = RandomGenerator.newValue(10); + + subscribeToChannel(pubNub, expectedChannel); + publishMessages(expectedChannel, 20); + pause(1); + + pubNub.history() + .channel(expectedChannel) + .count(10) + .async((result, status) -> { + assertFalse(status.isError()); + assertNotNull(result); + final int numberOfMessages = result.getMessages().size(); + assertEquals(10, numberOfMessages); + success.set(true); + }); + + success.set(true); + } + + @Test + public void testLoadingHistoryWithSpecificTimeInterval() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedChannel = RandomGenerator.newValue(10); + + subscribeToChannel(pubNub, expectedChannel); + + final Long before = System.currentTimeMillis() * 10_000; + pause(5); + publishMessages(expectedChannel, 3); + pause(5); + final Long now = System.currentTimeMillis() * 10_000; + + pubNub.history() + .channel(expectedChannel) + .includeTimetoken(true) + .start(now) + .end(before) + .count(10) + .async((result, status) -> { + assertFalse(status.isError()); + assertNotNull(result); + assertEquals(3, result.getMessages().size()); + success.set(true); + }); + success.set(true); + } + + @Test + public void testReverseHistoryPaging() { + final AtomicBoolean success = new AtomicBoolean(); + final String channel = RandomGenerator.newValue(10); + final String message_1 = RandomGenerator.newValue(20); + final String message_2 = RandomGenerator.newValue(20); + + subscribeToChannel(pubNub, channel); + publishMessage(pubNub, channel, message_1); + publishMessage(pubNub, channel, message_2); + pause(1); + + pubNub.history() + .channel(channel) + .count(10) + .reverse(true) + .async((result, status) -> { + assertFalse(status.isError()); + assertNotNull(result); + if (result.getMessages().size() > 0) { + final String message = result.getMessages().get(0).getEntry().toString(); + assertTrue(message.contains(message_1)); + } else { + fail("Messages are empty"); + } + success.set(true); + }); + + success.set(true); + } + + private void publishMessages(String channel, int counter) { + for (int i = 0; i < counter; i++) { + publishMessage(pubNub, channel, RandomGenerator.newValue(10)); + } + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/StreamFilteringIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/StreamFilteringIntegrationTests.java new file mode 100644 index 000000000..e531ab985 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/StreamFilteringIntegrationTests.java @@ -0,0 +1,408 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class StreamFilteringIntegrationTests extends BaseIntegrationTest { + + private final Map metaEnglish = getMetaLanguageFilter("en"); + private final Map metaFrench = getMetaLanguageFilter("fr"); + private final Map metaSpanish = getMetaLanguageFilter("es"); + private final Map metaGerman = getMetaLanguageFilter("de"); + private final Map metaItalian = getMetaLanguageFilter("it"); + + private final Map metaTemperature_25 = getMetaTempatureFilter("25"); + private final Map metaTemperature_35 = getMetaTempatureFilter("35"); + private final Map metaTemperature_55 = getMetaTempatureFilter("55"); + + private final String messageEnglish = "This is just another message in English"; + private final String messageFrench = "This is just another message in French"; + private final String messageItalian = "This is just another message in Italian"; + private final String messageGerman = "This is just another message in German"; + private final String messageSpanish = "This is just another message in Spanish"; + + @Override + protected void onBefore() { + + } + + @Override + protected void onAfter() { + + } + + @Test + public void testSubscribeWithLanguageFiltering() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.getConfiguration().setFilterExpression("language == 'en'"); + + final String channel = randomChannel(); + + subscribeToChannel(pubNub, channel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + assertTrue(message.getMessage().toString().contains(messageEnglish)); + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + publishMessage(pubNub, channel, messageFrench, metaFrench); + publishMessage(pubNub, channel, messageGerman, metaGerman); + publishMessage(pubNub, channel, messageEnglish, metaEnglish); + + listen(success); + } + + @Test + public void testSubscribeWithMultipleLanguageFiltering() { + final AtomicInteger success = new AtomicInteger(0); + pubNub.getConfiguration().setFilterExpression("('fr', 'en', 'de') contains language"); + + final String channel = randomChannel(); + subscribeToChannel(pubNub, channel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + success.incrementAndGet(); + assertFalse(message.getMessage().toString().contains("italian")); + assertFalse(message.getMessage().toString().contains("spanish")); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + publishMessage(pubNub, channel, messageFrench, metaFrench); + publishMessage(pubNub, channel, messageEnglish, metaEnglish); + publishMessage(pubNub, channel, messageItalian, metaItalian); + publishMessage(pubNub, channel, messageSpanish, metaSpanish); + publishMessage(pubNub, channel, messageGerman, metaGerman); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(success, equalTo(3)); + } + + @Test + public void testSubscribeWithLanguageNegationFiltering() { + final AtomicInteger success = new AtomicInteger(0); + + pubNub.getConfiguration().setFilterExpression("language != 'en'"); + + final String channel = randomChannel(); + subscribeToChannel(pubNub, channel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + success.incrementAndGet(); + assertFalse(message.getMessage().toString().contains("english")); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + publishMessage(pubNub, channel, messageFrench, metaFrench); + publishMessage(pubNub, channel, messageEnglish, metaEnglish); + publishMessage(pubNub, channel, messageItalian, metaItalian); + publishMessage(pubNub, channel, messageGerman, metaGerman); + publishMessage(pubNub, channel, messageSpanish, metaSpanish); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(success, equalTo(4)); + } + + @Test + public void testSubscribeWithGreaterThanFiltering() { + final AtomicInteger success = new AtomicInteger(0); + + pubNub.getConfiguration().setFilterExpression("temperature > 50"); + + final String channel = randomChannel(); + subscribeToChannel(pubNub, channel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + success.incrementAndGet(); + assertFalse(message.getMessage().toString().contains("25")); + assertFalse(message.getMessage().toString().contains("35")); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + final String messageTemp25 = "This is just message for today temperature : 25"; + publishMessage(pubNub, channel, messageTemp25, metaTemperature_25); + final String messageTemp35 = "This is just message for today temperature : 35"; + publishMessage(pubNub, channel, messageTemp35, metaTemperature_35); + final String messageTemp55 = "This is just message for today temperature : 55"; + publishMessage(pubNub, channel, messageTemp55, metaTemperature_55); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(success, equalTo(1)); + } + + @Test + public void testSubscribeWithLikeFiltering() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.getConfiguration().setFilterExpression("message_part LIKE '*success*'"); + + final String channel = randomChannel(); + subscribeToChannel(pubNub, channel); + + final String messageBoring = "This is just another boring message!"; + final Map metaMessage_1_Part = getMetaLikeFilter(messageBoring); + final String messageSuccess = "This is just another successful message :)"; + final Map metaMessage_2_Part = getMetaLikeFilter(messageSuccess); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + boolean correctMessage = false; + if (message.getMessage().toString().contains("success")) { + correctMessage = true; + } + assertTrue(correctMessage); + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + publishMessage(pubNub, channel, messageBoring, metaMessage_1_Part); + publishMessage(pubNub, channel, messageSuccess, metaMessage_2_Part); + + listen(success); + } + + private Map getMetaLanguageFilter(String metaFilter) { + final Map meta = new HashMap<>(); + meta.put("language", metaFilter); + return meta; + } + + private Map getMetaTempatureFilter(String metaFilter) { + final Map meta = new HashMap<>(); + meta.put("temperature", metaFilter); + return meta; + } + + private Map getMetaLikeFilter(String metaFilter) { + final Map meta = new HashMap<>(); + meta.put("message_part", metaFilter); + return meta; + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/SubscribeIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/SubscribeIntegrationTests.java new file mode 100644 index 000000000..d4f6b5516 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/SubscribeIntegrationTests.java @@ -0,0 +1,258 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SubscribeIntegrationTests extends BaseIntegrationTest { + + private PubNub mGuestClient; + + @Override + protected void onBefore() { + mGuestClient = getPubNub(); + } + + @Override + protected void onAfter() { + + } + + @Test + public void testSubscribeToMultipleChannels() { + final String channel1 = randomChannel(); + final String channel2 = randomChannel(); + final String channel3 = randomChannel(); + + pubNub.subscribe() + .channels(Arrays.asList(channel1, channel2, channel3)) + .withPresence() + .execute(); + + pause(2); + + assertEquals(3, pubNub.getSubscribedChannels().size()); + assertTrue(pubNub.getSubscribedChannels().contains(channel1)); + assertTrue(pubNub.getSubscribedChannels().contains(channel2)); + assertTrue(pubNub.getSubscribedChannels().contains(channel3)); + } + + @Test + public void testSubscribeToChannel() { + final String channel = randomChannel(); + + pubNub.subscribe() + .channels(Collections.singletonList(channel)) + .withPresence() + .execute(); + + pause(2); + + assertEquals(1, pubNub.getSubscribedChannels().size()); + assertTrue(pubNub.getSubscribedChannels().contains(channel)); + } + + // If test does not work make sure you've enabled wildcard + @Test + public void testWildcardSubscribe() { + final AtomicBoolean success = new AtomicBoolean(); + + subscribeToChannel(pubNub, "my.*"); + subscribeToChannel(mGuestClient, "my.test"); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + assertTrue(message.getMessage().toString().contains("Cool message")); + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + publishMessage(mGuestClient, "my.test", "Cool message!"); + + listen(success); + } + + @Test + public void testUnsubscribeFromChannel() { + final AtomicBoolean success = new AtomicBoolean(); + + final String expectedChannel = randomChannel(); + + subscribeToChannel(pubNub, expectedChannel); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + boolean channelSubscribed = false; + for (int i = 0; i < pubnub.getSubscribedChannels().size(); i++) { + if (pubnub.getSubscribedChannels().get(i).contains(expectedChannel)) { + channelSubscribed = true; + } + } + assertFalse(channelSubscribed); + + pubNub.removeListener(this); + success.set(true); + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + success.set(true); + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + unsubscribeFromChannel(pubNub, expectedChannel); + + listen(success); + } + + @Test + public void testUnsubscribeFromAllChannels() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + assertEquals(0, pubNub.getSubscribedChannels().size()); + success.set(true); + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + success.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + success.set(true); + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubNub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + }); + + unsubscribeFromAllChannels(pubNub); + listen(success); + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/TimeIntegrationTests.java b/src/integrationTest/java/com/pubnub/api/integration/TimeIntegrationTests.java new file mode 100644 index 000000000..964b6b2d9 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/TimeIntegrationTests.java @@ -0,0 +1,26 @@ +package com.pubnub.api.integration; + +import com.pubnub.api.integration.util.BaseIntegrationTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + + +public class TimeIntegrationTests extends BaseIntegrationTest { + + @Test + public void testGetPubNubTime() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.time() + .async((result, status) -> { + assert result != null; + Assert.assertNotNull(result.getTimetoken()); + Assert.assertFalse(status.isError()); + success.set(true); + }); + + listen(success); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/AbstractReconnectionProblem.java b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/AbstractReconnectionProblem.java new file mode 100644 index 000000000..7554af0c2 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/AbstractReconnectionProblem.java @@ -0,0 +1,424 @@ +package com.pubnub.api.integration.managers.subscription; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.integration.util.ITTestConfig; +import com.pubnub.api.models.consumer.PNStatus; +import org.aeonbits.owner.ConfigFactory; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.AssumptionViolatedException; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class AssumingProperConfig implements TestRule { + private ITTestConfig itPamTestConfig = ConfigFactory.create(ITTestConfig.class, System.getenv()); + + @NotNull + @Override + public Statement apply(@NotNull final Statement base, @NotNull final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (itPamTestConfig.pamSecKey() != null) { + base.evaluate(); + } + else { + throw new AssumptionViolatedException("missing config"); + } + } + }; + } +} + +public abstract class AbstractReconnectionProblem { + protected ITTestConfig itPamTestConfig = ConfigFactory.create(ITTestConfig.class, System.getenv()); + + @ClassRule + public static AssumingProperConfig assumingProperConfig = new AssumingProperConfig(); + + protected static final int SUBSCRIBE_TIMEOUT = 5; + protected final List collectedStatuses = Collections.synchronizedList(new ArrayList<>()); + protected PubNub pn; + + protected String authKey = randomId(); + + private static String randomId() { + return UUID.randomUUID().toString(); + } + + private void grantAccess(final String... protectedChannelNames) throws PubNubException { + final PubNub pnAdmin = adminPubNub(); + pnAdmin.grant() + .authKeys(singletonList(authKey)) + .channels(asList(protectedChannelNames)) + .read(true) + .sync(); + } + + private void grantAccessToChannelGroup(final String... protectedChannelGroupNames) throws PubNubException { + final PubNub pnAdmin = adminPubNub(); + pnAdmin.grant() + .authKeys(singletonList(authKey)) + .channelGroups(asList(protectedChannelGroupNames)) + .read(true) + .sync(); + } + + @Before + public void setupClients() { + pn = spy(privilegedClientPubNub()); + } + + @After + public void disconnectClients() { + pn.forceDestroy(); + pn = null; + } + + protected void subscribe(final PubNub pnClient, final boolean reportCallStack, final String... channels) { + subscribe(pnClient, reportCallStack, null, channels); + } + + protected void subscribe(final PubNub pnClient, final String... channelNames) { + subscribe(pnClient, false, null, channelNames); + } + + protected void subscribe(final PubNub pnClient, final BiConsumer block, final String... channels) { + subscribe(pnClient, false, block, channels); + } + + protected void subscribe(final PubNub pnClient, boolean reportCallStack, final BiConsumer block, final String... channels) { + pnClient.addListener(new SubscribeCallbackAdapter() { + @Override + public void status(final PubNub pubnub, final PNStatus pnStatus) { + final Exception exception = new Exception(); + synchronized (collectedStatuses) { + collectedStatuses.add(new CollectedStatus(pnStatus, exception)); + } + System.out.println("status: " + pnStatus); + System.out.println("affected channels: " + pnStatus.getAffectedChannels()); + if (reportCallStack) { + exception.printStackTrace(System.out); + } + if (block != null) { + block.accept(pubnub, pnStatus); + } + } + }); + + pnClient.subscribe().channels(asList(channels)).execute(); + } + + protected void subscribeToGroup(final PubNub pnClient, final boolean reportCallStack, final String... channelGroups) { + subscribeToGroup(pnClient, reportCallStack, null, channelGroups); + } + + protected void subscribeToGroup(final PubNub pnClient, final String... channelGroups) { + subscribeToGroup(pnClient, false, null, channelGroups); + } + + protected void subscribeToGroup(final PubNub pnClient, final BiConsumer block, final String... channelGroups) { + subscribeToGroup(pnClient, false, block, channelGroups); + } + + protected void subscribeToGroup(final PubNub pnClient, boolean reportCallStack, final BiConsumer block, final String... channelGroups) { + pnClient.addListener(new SubscribeCallbackAdapter() { + @Override + public void status(final PubNub pubnub, final PNStatus pnStatus) { + final Exception exception = new Exception(); + synchronized (collectedStatuses) { + collectedStatuses.add(new CollectedStatus(pnStatus, exception)); + } + System.out.println("status: " + pnStatus); + System.out.println("affected channels: " + pnStatus.getAffectedChannels()); + System.out.println("affected channel groups: " + pnStatus.getAffectedChannelGroups()); + if (reportCallStack) { + exception.printStackTrace(System.out); + } + if (block != null) { + block.accept(pubnub, pnStatus); + } + } + }); + + pnClient.subscribe() + .channelGroups(asList(channelGroups)) + .execute(); + } + + + + protected void createChannelGroup(final PubNub pnClient, final String channelGroup, final String... channelNames) throws PubNubException { + pnClient.addChannelsToChannelGroup() + .channelGroup(channelGroup) + .channels(asList(channelNames)) + .sync(); + } + + protected abstract PubNub privilegedClientPubNub(); + + private PubNub adminPubNub() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(itPamTestConfig.pamSubKey()); + pnConfiguration.setPublishKey(itPamTestConfig.pamPubKey()); + pnConfiguration.setSecretKey(itPamTestConfig.pamSecKey()); + return new PubNub(pnConfiguration); + } + + @Test + public void alwaysContinueSubscriptionToChannelGroupIfNoActionTaken() throws PubNubException, InterruptedException { + final String channelGroup = "chg-1-" + randomId(); + + createChannelGroup(adminPubNub(), channelGroup, "ch-1-" + randomId(), "ch-2-" + randomId()); + + subscribeToGroup(pn, true, channelGroup); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannelGroups().contains(channelGroup)) + .count(); + + assertThat(countAccessDenied, greaterThan(1L)); + } + + + @Test + public void alwaysContinueSubscriptionIfNoActionTaken() throws InterruptedException { + final String channel = "ch-" + randomId(); + + subscribe(pn, true, channel); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannels().contains(channel)) + .count(); + + assertThat(countAccessDenied, greaterThan(1L)); + } + + @Test + public void continueSubscriptionAfterUnsubscribeFromForbiddenChannel() throws InterruptedException, PubNubException { + final String channel1 = "ch-1-" + randomId(); + final String channel2 = "ch-2-" + randomId(); + + grantAccess(channel1); + + subscribe(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + final List channelsToUnsubscribe = status.getAffectedChannels(); + try { + System.out.println("Unsubscribing from: " + channelsToUnsubscribe); + pubNub.unsubscribe().channels(channelsToUnsubscribe).execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + }, channel1, channel2); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannels().contains(channel2)) + .count(); + + long countConnected = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNConnectedCategory + && collectedStatus.getPnStatus().getAffectedChannels().contains(channel1)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + assertThat(countConnected, equalTo(1L)); + } + + @Test + public void continueSubscriptionToChannelGroupAfterUnsubscribeFromForbiddenChannel() throws InterruptedException, PubNubException { + final String channelGroup1 = "chg-1-" + randomId(); + final String channelGroup2 = "chg-2-" + randomId(); + + createChannelGroup(adminPubNub(), channelGroup1, "ch-1-" + randomId(), "ch-2-" + randomId()); + createChannelGroup(adminPubNub(), channelGroup2, "ch-1-" + randomId(), "ch-2-" + randomId()); + + grantAccessToChannelGroup(channelGroup1); + + subscribeToGroup(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + final List channelGroupsToUnsubscribe = status.getAffectedChannelGroups(); + try { + System.out.println("Unsubscribing from groups: " + channelGroupsToUnsubscribe); + pubNub.unsubscribe().channelGroups(channelGroupsToUnsubscribe).execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + }, channelGroup1, channelGroup2); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannelGroups().contains(channelGroup2)) + .count(); + + long countConnected = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNConnectedCategory + && collectedStatus.getPnStatus().getAffectedChannelGroups().contains(channelGroup1)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + assertThat(countConnected, equalTo(1L)); + } + + + @Test + public void stopSubscriptionWhenRequestedToDisconnectOnAccessDenied() throws InterruptedException { + final String channel = "ch-" + randomId(); + + subscribe(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + pn.disconnect(); + } + } + } + }, channel); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannels().contains(channel)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + verify(pn, times(1)).disconnect(); + } + + @Test + public void stopSubscriptionToChannelGroupWhenRequestedToDisconnectOnAccessDenied() throws InterruptedException { + final String channelGroup = "chg-" + randomId(); + + subscribeToGroup(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + pn.disconnect(); + } + } + } + }, channelGroup); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannelGroups().contains(channelGroup)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + verify(pn, times(1)).disconnect(); + } + + + @Test + public void stopSubscriptionWhenRequestedToForceDestroyOnAccessDenied() throws InterruptedException { + final String channel = "ch-" + randomId(); + + subscribe(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + pn.forceDestroy(); + } + } + } + }, channel); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannels().contains(channel)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + verify(pn, times(1)).forceDestroy(); + } + + @Test + public void stopSubscriptionToChannelGroupWhenRequestedToForceDestroyOnAccessDenied() throws InterruptedException { + final String channelGroup = "chg-" + randomId(); + + subscribeToGroup(pn, true, new BiConsumer() { + @Override + public void accept(final PubNub pubNub, final PNStatus status) { + if (status.isError()) { + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + pn.forceDestroy(); + } + } + } + }, channelGroup); + + TimeUnit.SECONDS.sleep(SUBSCRIBE_TIMEOUT * 3); + + long countAccessDenied = collectedStatuses.stream() + .filter(collectedStatus -> + collectedStatus.getPnStatus().getCategory() == PNStatusCategory.PNAccessDeniedCategory + && collectedStatus.getPnStatus().getAffectedChannelGroups().contains(channelGroup)) + .count(); + + assertThat(countAccessDenied, equalTo(1L)); + verify(pn, times(1)).forceDestroy(); + } +} \ No newline at end of file diff --git a/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/CollectedStatus.java b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/CollectedStatus.java new file mode 100644 index 000000000..e0d0a03ce --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/CollectedStatus.java @@ -0,0 +1,48 @@ +package com.pubnub.api.integration.managers.subscription; + +import com.pubnub.api.models.consumer.PNStatus; + +import java.time.Instant; +import java.util.Objects; + +public class CollectedStatus { + private final Instant timestamp; + private final Exception exception; + private final PNStatus pnStatus; + + public CollectedStatus(final PNStatus pnStatus, final Exception exception) { + this.timestamp = Instant.now(); + this.pnStatus = pnStatus; + this.exception = exception; + } + + public Instant getTimestamp() { + return timestamp; + } + + public Exception getException() { + return exception; + } + + public StackTraceElement[] getStackTrace() { + return exception.getStackTrace(); + } + + public PNStatus getPnStatus() { + return pnStatus; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final CollectedStatus that = (CollectedStatus) o; + return timestamp.equals(that.timestamp) && + pnStatus.equals(that.pnStatus); + } + + @Override + public int hashCode() { + return Objects.hash(timestamp, pnStatus); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithReconnectionPolicy.java b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithReconnectionPolicy.java new file mode 100644 index 000000000..ff7168e8f --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithReconnectionPolicy.java @@ -0,0 +1,22 @@ +package com.pubnub.api.integration.managers.subscription; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.enums.PNLogVerbosity; + +import static com.pubnub.api.enums.PNReconnectionPolicy.LINEAR; + + +public class ReconnectionProblemWithReconnectionPolicy extends AbstractReconnectionProblem { + @Override + protected PubNub privilegedClientPubNub() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(itPamTestConfig.pamSubKey()); + pnConfiguration.setPublishKey(itPamTestConfig.pamPubKey()); + pnConfiguration.setSubscribeTimeout(5); + pnConfiguration.setLogVerbosity(PNLogVerbosity.BODY); + pnConfiguration.setReconnectionPolicy(LINEAR); + pnConfiguration.setAuthKey(authKey); + return new PubNub(pnConfiguration); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithoutReconnectionPolicy.java b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithoutReconnectionPolicy.java new file mode 100644 index 000000000..ea3af4465 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/ReconnectionProblemWithoutReconnectionPolicy.java @@ -0,0 +1,22 @@ +package com.pubnub.api.integration.managers.subscription; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; + +import static com.pubnub.api.enums.PNLogVerbosity.BODY; +import static com.pubnub.api.enums.PNReconnectionPolicy.NONE; + + +public class ReconnectionProblemWithoutReconnectionPolicy extends AbstractReconnectionProblem { + @Override + protected PubNub privilegedClientPubNub() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(itPamTestConfig.pamSubKey()); + pnConfiguration.setPublishKey(itPamTestConfig.pamPubKey()); + pnConfiguration.setSubscribeTimeout(SUBSCRIBE_TIMEOUT); + pnConfiguration.setLogVerbosity(BODY); + pnConfiguration.setAuthKey(authKey); + pnConfiguration.setReconnectionPolicy(NONE); + return new PubNub(pnConfiguration); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/SubscribeCallbackAdapter.java b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/SubscribeCallbackAdapter.java new file mode 100644 index 000000000..3a07f40f2 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/managers/subscription/SubscribeCallbackAdapter.java @@ -0,0 +1,61 @@ +package com.pubnub.api.integration.managers.subscription; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; + +public class SubscribeCallbackAdapter extends SubscribeCallback { + + @Override + public void status(final PubNub pubnub, final PNStatus pnStatus) { + + } + + @Override + public void message(final PubNub pubnub, final PNMessageResult pnMessageResult) { + + } + + @Override + public void presence(final PubNub pubnub, final PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(final PubNub pubnub, final PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(final PubNub pubnub, final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(final PubNub pubnub, final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(final PubNub pubnub, final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(final PubNub pubnub, final PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(final PubNub pubnub, final PNFileEventResult pnFileEventResult) { + + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiBaseIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiBaseIT.java new file mode 100644 index 000000000..1ab036def --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiBaseIT.java @@ -0,0 +1,32 @@ +package com.pubnub.api.integration.objects; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.enums.PNLogVerbosity; +import com.pubnub.api.integration.util.ITTestConfig; +import org.aeonbits.owner.ConfigFactory; +import org.junit.Before; + +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assume.assumeThat; + +public abstract class ObjectsApiBaseIT { + //See README.md in integrationTest directory for more info on running integration tests + private ITTestConfig itTestConfig = ConfigFactory.create(ITTestConfig.class, System.getenv()); + + protected final PubNub pubNubUnderTest = pubNub(); + + private PubNub pubNub() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(itTestConfig.subscribeKey()); + pnConfiguration.setLogVerbosity(PNLogVerbosity.BODY); + + return new PubNub(pnConfiguration); + } + + @Before + public void assumeTestsAreConfiguredProperly() { + assumeThat("Subscription key must be set in test.properties", itTestConfig.subscribeKey(), not(isEmptyOrNullString())); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiSubscriptionIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiSubscriptionIT.java new file mode 100644 index 000000000..4efdaf8ba --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/ObjectsApiSubscriptionIT.java @@ -0,0 +1,184 @@ +package com.pubnub.api.integration.objects; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +public class ObjectsApiSubscriptionIT extends ObjectsApiBaseIT { + private final String TEST_CHANNEL = UUID.randomUUID().toString(); + + class TestSubscribeCallbackAdapter extends SubscribeCallback { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) {} + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) {} + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) {} + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) {} + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) {} + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) {} + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) {} + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) {} + + @Override + public void file(@NotNull final PubNub pubnub, @NotNull final PNFileEventResult pnFileEventResult) {} + } + + @Test + public void receivingCallbackObjectsHasBeenSet() throws Exception { + //given + final AtomicReference uuidMetadataResultHolder = new AtomicReference<>(); + final AtomicReference channelMetadataResultHolder = new AtomicReference<>(); + final AtomicReference membershipResultHolder = new AtomicReference<>(); + + pubNubUnderTest.addListener(new TestSubscribeCallbackAdapter() { + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + uuidMetadataResultHolder.set(pnUUIDMetadataResult); + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + channelMetadataResultHolder.set(pnChannelMetadataResult); + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + membershipResultHolder.set(pnMembershipResult); + } + }); + //when + pubNubUnderTest.subscribe() + .channels(Collections.singletonList(TEST_CHANNEL)) + .execute(); + + pubNubUnderTest.setChannelMetadata() + .channel(TEST_CHANNEL) + .name("The Channel") + .description("This is test description") + .sync(); + + pubNubUnderTest.setMemberships() + .channelMemberships(Collections.singletonList(PNChannelMembership.channel(TEST_CHANNEL))) + .sync(); + + final String testName = "Test Name"; + pubNubUnderTest.setUUIDMetadata().name(testName) + .sync(); + + //then + await().atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> { + final PNUUIDMetadataResult receivedUUIDMetadataResult = uuidMetadataResultHolder.get(); + final PNChannelMetadataResult receivedChannelMetadataResult = channelMetadataResultHolder.get(); + final PNMembershipResult receivedMembershipResult = membershipResultHolder.get(); + assertThat(receivedUUIDMetadataResult, + allOf(notNullValue(), hasProperty("event", is("set")))); + assertThat(receivedChannelMetadataResult, + allOf(notNullValue(), hasProperty("event", is("set")))); + assertThat(receivedMembershipResult, + allOf(notNullValue(), hasProperty("event", is("set")))); + }); + + } + + @Test + public void receivingCallbackObjectsHasBeenUnset() throws Exception { + //given + final AtomicReference uuidMetadataResultHolder = new AtomicReference<>(); + final AtomicReference channelMetadataResultHolder = new AtomicReference<>(); + final AtomicReference membershipResultHolder = new AtomicReference<>(); + + pubNubUnderTest.addListener(new TestSubscribeCallbackAdapter() { + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + uuidMetadataResultHolder.set(pnUUIDMetadataResult); + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + channelMetadataResultHolder.set(pnChannelMetadataResult); + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + membershipResultHolder.set(pnMembershipResult); + } + }); + pubNubUnderTest.setChannelMetadata() + .channel(TEST_CHANNEL) + .name("The Channel") + .description("This is test description") + .sync(); + + pubNubUnderTest.setMemberships() + .channelMemberships(Collections.singletonList(PNChannelMembership.channel(TEST_CHANNEL))) + .sync(); + + final String testName = "Test Name"; + pubNubUnderTest.setUUIDMetadata().name(testName) + .sync(); + + pubNubUnderTest.subscribe() + .channels(Collections.singletonList(TEST_CHANNEL)) + .execute(); + //when + pubNubUnderTest.removeUUIDMetadata().sync(); + + pubNubUnderTest.removeMemberships() + .channelMemberships(Collections.singletonList(PNChannelMembership.channel(TEST_CHANNEL))) + .sync(); + + pubNubUnderTest.removeChannelMetadata() + .channel(TEST_CHANNEL) + .sync(); + + //then + await().atMost(2, TimeUnit.SECONDS) + .untilAsserted(() -> { + final PNUUIDMetadataResult receivedUUIDMetadataResult = uuidMetadataResultHolder.get(); + final PNChannelMetadataResult receivedChannelMetadataResult = channelMetadataResultHolder.get(); + final PNMembershipResult receivedMembershipResult = membershipResultHolder.get(); + assertThat(receivedUUIDMetadataResult, + allOf(notNullValue(), hasProperty("event", is("delete")))); + assertThat(receivedChannelMetadataResult, + allOf(notNullValue(), hasProperty("event", is("delete")))); + assertThat(receivedMembershipResult, + allOf(notNullValue(), hasProperty("event", is("delete")))); + }); + + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/channel/ChannelMetadataIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/channel/ChannelMetadataIT.java new file mode 100644 index 000000000..5b6a25b12 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/channel/ChannelMetadataIT.java @@ -0,0 +1,251 @@ +package com.pubnub.api.integration.objects.channel; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.channel.PNGetAllChannelsMetadataResult; +import com.pubnub.api.models.consumer.objects_api.channel.PNGetChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.channel.PNRemoveChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.channel.PNSetChannelMetadataResult; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.http.HttpStatus; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class ChannelMetadataIT extends ObjectsApiBaseIT { + private final static Logger LOG = LoggerFactory.getLogger(ChannelMetadataIT.class); + private static final int NUMBER_OF_RANDOM_TEST_NAMES = 10; + private static final int FETCH_LIMIT = 3; + + private final List randomChannelMetadataIds = randomChannelMetadataIds(); + private final String randomChannelMetadataId = randomChannelMetadataIds.get(0); + + private final String randomDescription = randomDescription(); + + private final List createdChannelMetadataList = new ArrayList<>(); + + @Test + public void setChannelHappyPath() throws PubNubException { + //given + + //when + final PNSetChannelMetadataResult setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(randomChannelMetadataId) + .description(randomDescription) + .custom(customChannelObject()) + .includeCustom(true) + .sync(); + + //then + assertNotNull(setChannelMetadataResult); + assertEquals(HttpStatus.SC_OK, setChannelMetadataResult.getStatus()); + createdChannelMetadataList.add(setChannelMetadataResult); + assertEquals(randomChannelMetadataId, setChannelMetadataResult.getData().getId()); + assertEquals(setChannelMetadataResult.getData().getDescription(), + setChannelMetadataResult.getData().getDescription()); + assertNotNull(setChannelMetadataResult.getData().getCustom()); + } + + @Test + public void getChannelHappyPath() throws PubNubException { + //given + final PNSetChannelMetadataResult setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(randomChannelMetadataId) + .description(randomDescription) + .custom(customChannelObject()) + .includeCustom(true) + .sync(); + createdChannelMetadataList.add(setChannelMetadataResult); + + //when + final PNGetChannelMetadataResult getChannelMetadataResult = pubNubUnderTest.getChannelMetadata() + .channel(randomChannelMetadataId) + .includeCustom(true) + .sync(); + + //then + assertNotNull(getChannelMetadataResult); + assertEquals(HttpStatus.SC_OK, getChannelMetadataResult.getStatus()); + + assertEquals(randomChannelMetadataId, getChannelMetadataResult.getData().getId()); + assertEquals(setChannelMetadataResult.getData().getName(), getChannelMetadataResult.getData().getName()); + assertEquals(setChannelMetadataResult.getData().getDescription(), + getChannelMetadataResult.getData().getDescription()); + assertNotNull(setChannelMetadataResult.getData().getCustom()); + } + + @Test + public void getAllChannelHappyPath() throws PubNubException { + //given + + for (String testChannelName: randomChannelMetadataIds) { + final PNSetChannelMetadataResult setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(testChannelName) + .description(randomDescription) + .custom(customChannelObject()) + .includeCustom(true) + .sync(); + createdChannelMetadataList.add(setChannelMetadataResult); + } + + //when + final PNGetAllChannelsMetadataResult getAllChannelsMetadataResult = pubNubUnderTest + .getAllChannelsMetadata() + .includeCustom(true) + .includeTotalCount(true) + .limit(FETCH_LIMIT) + .sync(); + + //then + assertThat(getAllChannelsMetadataResult, allOf( + notNullValue(), + hasProperty("status", equalTo(HttpStatus.SC_OK)), + hasProperty("data", allOf( + not(empty()), + hasSize(FETCH_LIMIT))), + hasProperty("totalCount", greaterThanOrEqualTo(NUMBER_OF_RANDOM_TEST_NAMES)), + hasProperty("next", not(isEmptyOrNullString())), + hasProperty("prev", isEmptyOrNullString()))); + } + + @Test + public void getAllChannelsTraversingPagesHappyPath() throws PubNubException { + //given + + for (String testChannelMetadataId: randomChannelMetadataIds) { + final PNSetChannelMetadataResult setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(testChannelMetadataId) + .description(randomDescription) + .custom(customChannelObject()) + .includeCustom(true) + .sync(); + createdChannelMetadataList.add(setChannelMetadataResult); + } + + //when + final PNGetAllChannelsMetadataResult firstGetAllChannelsMetadataResult = pubNubUnderTest + .getAllChannelsMetadata() + .includeCustom(true) + .includeTotalCount(true) + .limit(FETCH_LIMIT) + .sync(); + + + final PNGetAllChannelsMetadataResult secondGetAllChannelsMetadataResult = pubNubUnderTest + .getAllChannelsMetadata() + .includeCustom(true) + .includeTotalCount(true) + .limit(FETCH_LIMIT) + .page(firstGetAllChannelsMetadataResult.nextPage()) + .sync(); + + final PNGetAllChannelsMetadataResult firstAgainGetAllChannelsMetadataResult = pubNubUnderTest + .getAllChannelsMetadata() + .includeCustom(true) + .includeTotalCount(true) + .limit(FETCH_LIMIT) + .page(secondGetAllChannelsMetadataResult.nextPage()) //to illustrate that last overrides + .page(secondGetAllChannelsMetadataResult.previousPage()) + .sync(); + + //then + final List firstResultIds = extractChannelIds(firstGetAllChannelsMetadataResult); + final List secondResultIds = extractChannelIds(secondGetAllChannelsMetadataResult); + final List thirdResultIds = extractChannelIds(firstAgainGetAllChannelsMetadataResult); + + assertThat(firstResultIds, allOf( + containsInAnyOrder(thirdResultIds.toArray()), + not(containsInAnyOrder(secondResultIds.toArray())))); + } + + @Test + public void removeChannelHappyPath() throws PubNubException { + //given + final PNSetChannelMetadataResult setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(randomChannelMetadataId) + .description(randomDescription) + .custom(customChannelObject()) + .includeCustom(true) + .sync(); + createdChannelMetadataList.add(setChannelMetadataResult); + + //when + final PNRemoveChannelMetadataResult removeChannelMetadataResult = pubNubUnderTest + .removeChannelMetadata() + .channel(randomChannelMetadataId) + .sync(); + + //then + assertNotNull(removeChannelMetadataResult); + assertEquals(HttpStatus.SC_OK, removeChannelMetadataResult.getStatus()); + } + + @After + public void cleanUp() { + createdChannelMetadataList.forEach(setChannelMetadataResult -> { + try { + pubNubUnderTest.removeChannelMetadata() + .channel(setChannelMetadataResult.getData().getId()) + .sync(); + } + catch (Exception e) { + LOG.warn("Could not cleanup {}", setChannelMetadataResult, e); + } + }); + } + + private Map customChannelObject() { + final Map customMap = new HashMap<>(); + customMap.putIfAbsent("channel_param1", "val1"); + customMap.putIfAbsent("channel_param2", "val2"); + return customMap; + } + + private static String randomDescription() { + return RandomStringUtils.randomAlphabetic(50, 160); + } + + private static String randomChannelName() { + return RandomStringUtils.randomAlphabetic(5, 10); + } + + private static List randomChannelMetadataIds() { + final List uuids = new ArrayList<>(); + for (int i = 0; i < NUMBER_OF_RANDOM_TEST_NAMES; i++) { + uuids.add(randomChannelName()); + } + return uuids; + } + + @NotNull + private static List extractChannelIds(final PNGetAllChannelsMetadataResult pnGetAllChannelsMetadataResult) { + final List ids = new ArrayList<>(); + for (PNChannelMetadata pnChannelMetadata: pnGetAllChannelsMetadataResult.getData()) { + ids.add(pnChannelMetadata.getId()); + } + return ids; + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/members/ChannelMembersIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/members/ChannelMembersIT.java new file mode 100644 index 000000000..c3874ee92 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/members/ChannelMembersIT.java @@ -0,0 +1,229 @@ +package com.pubnub.api.integration.objects.members; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.member.*; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +import static com.pubnub.api.endpoints.objects_api.utils.Include.PNUUIDDetailsLevel.UUID_WITH_CUSTOM; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +public class ChannelMembersIT extends ObjectsApiBaseIT { + private final static Logger LOG = LoggerFactory.getLogger(ChannelMembersIT.class); + + private final List createdMembersList = new ArrayList<>(); + + private final String TEST_UUID1 = UUID.randomUUID().toString(); + private final String TEST_UUID2 = UUID.randomUUID().toString(); + + private final String testChannelId = UUID.randomUUID().toString(); + + @Test + public void addChannelMembersHappyPath() throws PubNubException { + //given + Map customMembershipObject = customChannelMembershipObject(); + final Collection channelMembers = Arrays.asList(PNUUID.uuid(TEST_UUID1), + PNUUID.uuidWithCustom(TEST_UUID2, customMembershipObject)); + + //when + final PNSetChannelMembersResult setChannelMembersResult = pubNubUnderTest.setChannelMembers() + .channel(testChannelId) + .uuids(channelMembers) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + + //then + assertNotNull(setChannelMembersResult); + assertEquals(HttpStatus.SC_OK, setChannelMembersResult.getStatus()); + createdMembersList.add(setChannelMembersResult); + final List returnedUUIDs = new ArrayList<>(); + for (final PNMembers pnMembers : setChannelMembersResult.getData()) { + final PNUUIDMetadata uuid = pnMembers.getUuid(); + final String id = uuid.getId(); + returnedUUIDs.add(id); + } + final List expectedUUIDs = new ArrayList<>(); + for (final PNUUID channelMember : channelMembers) { + final PNUUID.UUIDId uuid = channelMember.getUuid(); + final String id = uuid.getId(); + expectedUUIDs.add(id); + } + + final List receivedCustomObjects = new ArrayList<>(); + for (final PNMembers it : setChannelMembersResult.getData()) { + final Object custom = it.getCustom(); + if (custom != null) { + receivedCustomObjects.add(custom); + } + } + + assertThat(returnedUUIDs, containsInAnyOrder(expectedUUIDs.toArray())); + assertThat(receivedCustomObjects, hasSize(1)); + } + + @Test + public void getChannelMembersHappyPath() throws PubNubException { + //given + final Collection channelMembers = Arrays.asList(PNUUID.uuid(TEST_UUID1), + PNUUID.uuidWithCustom(TEST_UUID2, customChannelMembershipObject())); + + final PNSetChannelMembersResult setChannelMembersResult = pubNubUnderTest.setChannelMembers() + .channel(testChannelId) + .uuids(channelMembers) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + createdMembersList.add(setChannelMembersResult); + + //when + final PNGetChannelMembersResult getMembersResult = pubNubUnderTest.getChannelMembers() + .channel(testChannelId) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + + + //then + assertNotNull(getMembersResult); + assertEquals(HttpStatus.SC_OK, getMembersResult.getStatus()); + final List returnedUUIDs = new ArrayList<>(); + for (final PNMembers pnMembers : setChannelMembersResult.getData()) { + final PNUUIDMetadata uuid = pnMembers.getUuid(); + final String id = uuid.getId(); + returnedUUIDs.add(id); + } + final List expectedUUIDs = new ArrayList<>(); + for (final PNUUID channelMember : channelMembers) { + final PNUUID.UUIDId uuid = channelMember.getUuid(); + String id = uuid.getId(); + expectedUUIDs.add(id); + } + final List receivedCustomObjects = new ArrayList<>(); + for (final PNMembers it : setChannelMembersResult.getData()) { + final Object custom = it.getCustom(); + if (custom != null) { + receivedCustomObjects.add(custom); + } + } + assertThat(returnedUUIDs, containsInAnyOrder(expectedUUIDs.toArray())); + assertThat(receivedCustomObjects, hasSize(1)); + + + } + + @Test + public void removeChannelMembersHappyPath() throws PubNubException { + //given + final Collection channelMembers = Arrays.asList(PNUUID.uuid(TEST_UUID1), + PNUUID.uuidWithCustom(TEST_UUID2, customChannelMembershipObject())); + + final PNSetChannelMembersResult setChannelMembersResult = pubNubUnderTest.setChannelMembers() + .channel(testChannelId) + .uuids(channelMembers) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + createdMembersList.add(setChannelMembersResult); + + //when + final PNRemoveChannelMembersResult removeMembersResult = pubNubUnderTest + .removeChannelMembers() + .channel(testChannelId) + .uuids(Collections.singletonList(PNUUID.uuid(TEST_UUID2))) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + + //then + final List returnedUUIDs = new ArrayList<>(); + for (final PNMembers pnMembers : removeMembersResult.getData()) { + final PNUUIDMetadata uuid = pnMembers.getUuid(); + final String id = uuid.getId(); + returnedUUIDs.add(id); + } + + assertNotNull(removeMembersResult); + assertEquals(HttpStatus.SC_OK, removeMembersResult.getStatus()); + assertThat(returnedUUIDs, not(hasItem(TEST_UUID2))); + } + + @Test + public void manageChannelMembersHappyPath() throws PubNubException { + //given + final List channelMembersToRemove = Collections.singletonList( + PNUUID.uuidWithCustom(TEST_UUID1, customChannelMembershipObject())); + + final PNSetChannelMembersResult setChannelMembersResult = pubNubUnderTest.setChannelMembers() + .channel(testChannelId) + .uuids(channelMembersToRemove) + .sync(); + createdMembersList.add(setChannelMembersResult); + + final List channelMembersToSet = Collections.singletonList( + PNUUID.uuidWithCustom(TEST_UUID2, customChannelMembershipObject())); + + //when + final PNManageChannelMembersResult manageChannelMembersResult = pubNubUnderTest.manageChannelMembers() + .channel(testChannelId) + .set(channelMembersToSet) + .remove(channelMembersToRemove) + .includeTotalCount(true) + .includeCustom(true) + .includeUUID(UUID_WITH_CUSTOM) + .sync(); + createdMembersList.add(new PNSetChannelMembersResult(manageChannelMembersResult)); + + //then + assertThat(manageChannelMembersResult, allOf( + notNullValue(), + hasProperty("status", is(HttpStatus.SC_OK)), + hasProperty("data", allOf( + hasItem(hasProperty("uuid", + hasProperty("id", is(channelMembersToSet.get(0).getUuid().getId())))), + not(hasItem(hasProperty("uuid", + hasProperty("id", is(channelMembersToRemove.get(0).getUuid().getId()))))))))); + } + + @After + public void cleanUp() { + for (final PNSetChannelMembersResult createdMembers : createdMembersList) { + try { + final List channelMembers = new ArrayList<>(); + for (final PNMembers it : createdMembers.getData()) { + final String id = it.getUuid().getId(); + final PNUUID pnUUIDs = PNUUID.uuid(id); + channelMembers.add(pnUUIDs); + } + + pubNubUnderTest.removeChannelMembers() + .channel(testChannelId) + .uuids(channelMembers) + .sync(); + + } catch (Exception e) { + LOG.warn("Could not cleanup {}", createdMembers, e); + } + } + } + + private Map customChannelMembershipObject() { + final Map customMap = new HashMap<>(); + customMap.putIfAbsent("members_param1", "val1"); + customMap.putIfAbsent("members_param2", "val2"); + return customMap; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/members/CustomMetadataInMembersPropagationIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/members/CustomMetadataInMembersPropagationIT.java new file mode 100644 index 000000000..89c28037e --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/members/CustomMetadataInMembersPropagationIT.java @@ -0,0 +1,121 @@ +package com.pubnub.api.integration.objects.members; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.member.PNGetChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNSetChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult; +import org.junit.After; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static com.pubnub.api.endpoints.objects_api.utils.Include.PNUUIDDetailsLevel; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +public class CustomMetadataInMembersPropagationIT extends ObjectsApiBaseIT { + private final String testUUID = UUID.randomUUID().toString(); + private final String testChannelMetadataId = UUID.randomUUID().toString(); + private final String testExternalId = UUID.randomUUID().toString(); + private final Map testCustomObjectForUUIDMetadata = new HashMap() {{ + put("key1", "val1"); + put("key2", "val2"); + }}; + + private final Map testCustomObjectForMembers = new HashMap() {{ + put("key3", "val3"); + put("key4", "val4"); + }}; + private PNSetUUIDMetadataResult setUUIDMetadataResult; + private PNSetChannelMembersResult setChannelMembersResult; + + @Test + public void setMembersCustomHappyPath() throws PubNubException { + final String testProfileUrl = "http://example.com"; + final String testName = "Test Name"; + final String testEmail = "foo@example.com"; + + setUUIDMetadataResult = pubNubUnderTest.setUUIDMetadata() + .uuid(testUUID) + .name(testName) + .email(testEmail) + .externalId(testExternalId) + .profileUrl(testProfileUrl) + .custom(testCustomObjectForUUIDMetadata) + .includeCustom(true) + .sync(); + + setChannelMembersResult = pubNubUnderTest.setChannelMembers() + .channel(testChannelMetadataId) + .uuids(Collections.singletonList( + PNUUID.uuidWithCustom(setUUIDMetadataResult.getData().getId(), + testCustomObjectForMembers))) + .includeCustom(true) + .includeUUID(PNUUIDDetailsLevel.UUID_WITH_CUSTOM) + .sync(); + + final PNGetChannelMembersResult getChannelMembersResult = pubNubUnderTest.getChannelMembers() + .channel(testChannelMetadataId) + .includeCustom(false) + .includeUUID(PNUUIDDetailsLevel.UUID) + .sync(); + + assertThat(setUUIDMetadataResult, + hasProperty("data", hasProperty("custom", notNullValue()))); + + assertThat(setChannelMembersResult, hasProperty("data", + hasItem( + allOf( + hasProperty("custom", notNullValue()), + hasProperty("uuid", allOf( + allOf( + hasProperty("id", is(testUUID)), + hasProperty("name", is(testName)), + hasProperty("email", is(testEmail)), + hasProperty("externalId", is(testExternalId)), + hasProperty("profileUrl", is(testProfileUrl)), + hasProperty("custom", notNullValue())))))))); + assertThat(getChannelMembersResult, hasProperty("data", + hasItem( + allOf( + hasProperty("custom", nullValue()), + hasProperty("uuid", allOf( + allOf( + hasProperty("id", is(testUUID)), + hasProperty("name", is(testName)), + hasProperty("email", is(testEmail)), + hasProperty("externalId", is(testExternalId)), + hasProperty("profileUrl", is(testProfileUrl)), + hasProperty("custom", nullValue())))))))); + } + + @After + public void cleanUp() { + try { + if (setUUIDMetadataResult != null) { + if (setChannelMembersResult != null) { + pubNubUnderTest.removeChannelMembers() + .channel(testChannelMetadataId) + .uuids(Collections.singleton(PNUUID.uuid(setUUIDMetadataResult.getData().getId()))) + .sync(); + } + + pubNubUnderTest.removeUUIDMetadata() + .uuid(setUUIDMetadataResult.getData().getId()) + .sync(); + } + } catch (PubNubException e) { + e.printStackTrace(); + } + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java new file mode 100644 index 000000000..a3151aa94 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java @@ -0,0 +1,141 @@ +package com.pubnub.api.integration.objects.memberships; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.managers.subscription.SubscribeCallbackAdapter; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.channel.PNSetChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNGetMembershipsResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNSetMembershipResult; +import org.awaitility.core.ThrowingRunnable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import static com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel.CHANNEL; +import static com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +public class CustomMetadataInMembershipPropagationIT extends ObjectsApiBaseIT { + private final String testChannelMetadataId = UUID.randomUUID().toString(); + private final Map testCustomObjectForChannelMetadata = new HashMap() {{ + put("key1", "val1"); + put("key2", "val2"); + }}; + + private final Map testCustomObjectForMembership = new HashMap() {{ + put("key3", "val3"); + put("key4", "val4"); + }}; + private PNSetChannelMetadataResult setChannelMetadataResult; + private PNSetMembershipResult setMembershipResult; + + + private CopyOnWriteArrayList pnMembershipResults = new CopyOnWriteArrayList<>(); + + @Before + public void setCallbackListener() { + pubNubUnderTest.addListener(new SubscribeCallbackAdapter() { + @Override + public void membership(final PubNub pubnub, final PNMembershipResult pnMembershipResult) { + pnMembershipResults.add(pnMembershipResult); + } + }); + + pubNubUnderTest.subscribe() + .channels(Collections.singletonList(testChannelMetadataId)) + .execute(); + } + + @Test + public void setMembershipCustomHappyPath() throws PubNubException { + final String testChannelName = "The Name of the Channel"; + final String testDescription = "Some interesting channel description"; + setChannelMetadataResult = pubNubUnderTest.setChannelMetadata() + .channel(testChannelMetadataId) + .name(testChannelName) + .description(testDescription) + .custom(testCustomObjectForChannelMetadata) + .includeCustom(true) + .sync(); + + setMembershipResult = pubNubUnderTest.setMemberships() + .channelMemberships(Collections.singletonList( + PNChannelMembership.channelWithCustom(setChannelMetadataResult.getData().getId(), + testCustomObjectForMembership))) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + + final PNGetMembershipsResult getMembershipsResult = pubNubUnderTest.getMemberships() + .includeCustom(false) + .includeChannel(CHANNEL) + .sync(); + + assertThat(setChannelMetadataResult, + hasProperty("data", hasProperty("custom", notNullValue()))); + assertThat(setMembershipResult, hasProperty("data", + hasItem( + allOf( + hasProperty("custom", notNullValue()), + hasProperty("channel", allOf( + allOf( + hasProperty("id", is(testChannelMetadataId)), + hasProperty("name", is(testChannelName)), + hasProperty("description", is(testDescription)), + hasProperty("custom", notNullValue())))))))); + assertThat(getMembershipsResult, hasProperty("data", + hasItem( + allOf( + hasProperty("custom", nullValue()), + hasProperty("channel", allOf( + allOf( + hasProperty("id", is(testChannelMetadataId)), + hasProperty("name", is(testChannelName)), + hasProperty("description", is(testDescription)), + hasProperty("custom", nullValue())))))))); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(new ThrowingRunnable() { + @Override + public void run() throws Throwable { + assertThat(pnMembershipResults, hasItem( + hasProperty("data", hasProperty("uuid", is(pubNubUnderTest.getConfiguration().getUuid()))))); + } + }); + } + + @After + public void cleanUp() { + try { + if (setChannelMetadataResult != null) { + if (setMembershipResult != null) { + pubNubUnderTest.removeMemberships() + .channelMemberships(Collections.singletonList(PNChannelMembership.channel(setChannelMetadataResult.getData().getId()))) + .sync(); + } + + pubNubUnderTest.removeChannelMetadata() + .channel(setChannelMetadataResult.getData().getId()) + .sync(); + } + } catch (PubNubException e) { + e.printStackTrace(); + } + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/MembershipIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/MembershipIT.java new file mode 100644 index 000000000..be7c3a322 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/MembershipIT.java @@ -0,0 +1,215 @@ +package com.pubnub.api.integration.objects.memberships; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNGetMembershipsResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNManageMembershipResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNRemoveMembershipResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNSetMembershipResult; +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +public class MembershipIT extends ObjectsApiBaseIT { + private static final Logger LOG = LoggerFactory.getLogger(MembershipIT.class); + + public final String testChannelId1 = UUID.randomUUID().toString(); + public final String testChannelId2 = UUID.randomUUID().toString(); + + private final List createdMembershipsList = new ArrayList<>(); + + @Test + public void setMembershipsHappyPath() throws PubNubException { + //given + final List channelMemberships = Arrays.asList( + PNChannelMembership.channel(testChannelId1), + PNChannelMembership.channelWithCustom(testChannelId2, customChannelMembershipObject())); + + //when + final PNSetMembershipResult setMembershipResult = pubNubUnderTest.setMemberships() + .channelMemberships(channelMemberships) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + + //then + assertThat(setMembershipResult, allOf( + notNullValue(), + hasProperty("status", is(HttpStatus.SC_OK)) + )); + createdMembershipsList.add(setMembershipResult); + + assertThat(setMembershipResult, allOf( + hasProperty("data", + hasItem( + allOf( + hasProperty("channel", + hasProperty("id", is(testChannelId1))), + hasProperty("custom", nullValue())))), + hasProperty("data", + hasItem( + allOf( + hasProperty("channel", + hasProperty("id", is(testChannelId2))), + hasProperty("custom", notNullValue())))))); + } + + @Test + public void getMembershipsHappyPath() throws PubNubException { + //given + final List channelMemberships = Arrays.asList( + PNChannelMembership.channel(testChannelId1), + PNChannelMembership.channelWithCustom(testChannelId2, customChannelMembershipObject())); + + final PNSetMembershipResult setMembershipResult = pubNubUnderTest.setMemberships() + .channelMemberships(channelMemberships) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + createdMembershipsList.add(setMembershipResult); + + //when + final PNGetMembershipsResult getMembershipsResult = pubNubUnderTest.getMemberships() + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + + //then + assertThat(getMembershipsResult, allOf( + notNullValue(), + hasProperty("status", is(HttpStatus.SC_OK)), + hasProperty("data", + hasItem(hasProperty("channel", + hasProperty("id", is(testChannelId1))))), + hasProperty("data", + hasItem(hasProperty("channel", + hasProperty("id", is(testChannelId2))))))); + } + + @Test + public void removeMembershipsHappyPath() throws PubNubException { + //given + final List channelMemberships = Arrays.asList(PNChannelMembership.channel(testChannelId1), + PNChannelMembership.channelWithCustom(testChannelId2, customChannelMembershipObject())); + + final PNSetMembershipResult setMembershipResult = pubNubUnderTest.setMemberships() + .channelMemberships(channelMemberships) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + createdMembershipsList.add(setMembershipResult); + + //when + final PNRemoveMembershipResult removeMembershipResult = pubNubUnderTest.removeMemberships() + .channelMemberships(Collections.singletonList(PNChannelMembership.channel(testChannelId2))) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + + //then + assertThat(removeMembershipResult, allOf( + notNullValue(), + hasProperty("status", is(HttpStatus.SC_OK)), + hasProperty("data", + hasItem(hasProperty("channel", + hasProperty("id", is(testChannelId1))))), + hasProperty("data", + hasItem(hasProperty("channel", + not(hasProperty("id", is(testChannelId2)))))))); + + } + + @Test + public void manageMembershipsHappyPath() throws PubNubException { + //given + final List channelMembershipsToRemove = Collections.singletonList( + PNChannelMembership.channelWithCustom(testChannelId1, customChannelMembershipObject())); + + final PNSetMembershipResult setMembershipResult = pubNubUnderTest.setMemberships() + .channelMemberships(channelMembershipsToRemove) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + createdMembershipsList.add(setMembershipResult); + + final List channelMembershipsToSet = Collections.singletonList( + PNChannelMembership.channelWithCustom(testChannelId2, customChannelMembershipObject())); + + //when + final PNManageMembershipResult manageMembershipResult = pubNubUnderTest.manageMemberships() + .set(channelMembershipsToSet) + .remove(channelMembershipsToRemove) + .includeTotalCount(true) + .includeCustom(true) + .includeChannel(CHANNEL_WITH_CUSTOM) + .sync(); + + createdMembershipsList.add(new PNSetMembershipResult(manageMembershipResult)); + + //then + assertThat(manageMembershipResult, allOf( + notNullValue(), + hasProperty("status", is(HttpStatus.SC_OK)), + hasProperty("data", allOf( + hasItem(hasProperty("channel", + hasProperty("id", is(channelMembershipsToSet.get(0).getChannel().getId())))), + not(hasItem(hasProperty("channel", + hasProperty("id", is(channelMembershipsToRemove.get(0).getChannel().getId()))))))))); + } + + @After + public void cleanUp() { + for (final PNSetMembershipResult createdMembership : createdMembershipsList) { + try { + final List channelMemberships = new ArrayList<>(); + for (final PNMembership it : createdMembership.getData()) { + final String id = it.getChannel().getId(); + final PNChannelMembership pnChannelMembership = PNChannelMembership.channel(id); + channelMemberships.add(pnChannelMembership); + } + + pubNubUnderTest.removeMemberships() + .channelMemberships(channelMemberships) + .sync(); + + } catch (Exception e) { + LOG.warn("Could not cleanup {}", createdMembership, e); + } + } + } + + private static Map customChannelMembershipObject() { + final Map customMap = new HashMap<>(); + customMap.putIfAbsent("membership_param1", "val1"); + customMap.putIfAbsent("membership_param2", "val2"); + return customMap; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/uuid/UUIDMetadataIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/uuid/UUIDMetadataIT.java new file mode 100644 index 000000000..56eb5b4ba --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/uuid/UUIDMetadataIT.java @@ -0,0 +1,212 @@ +package com.pubnub.api.integration.objects.uuid; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.objects.ObjectsApiBaseIT; +import com.pubnub.api.models.consumer.objects_api.uuid.PNGetAllUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNGetUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNRemoveUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.http.HttpStatus; +import org.junit.After; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class UUIDMetadataIT extends ObjectsApiBaseIT { + private static final Logger LOG = LoggerFactory.getLogger(UUIDMetadataIT.class); + private static final int NUMBER_OF_RANDOM_TEST_UUIDS = 10; + private static final int FETCH_LIMIT = 3; + + private final List randomTestUUIDs = randomTestUUIDs(); + private final String randomTestUUID = randomTestUUIDs.get(0); + + private final List createdUUIDMetadataList = new ArrayList<>(); + private final String randomName = randomName(); + private final String randomEmail = randomEmail(); + private final String randomProfileUrl = randomProfileUrl(); + private final String randomExternalId = randomExternalId(); + + @Test + public void setUUIDHappyPath() throws PubNubException { + //given + + //when + final PNSetUUIDMetadataResult setUUIDMetadataResult = pubNubUnderTest.setUUIDMetadata() + .uuid(randomTestUUID) + .name(randomName) + .email(randomEmail) + .profileUrl(randomProfileUrl) + .externalId(randomExternalId) + .custom(customUUIDObject()) + .includeCustom(true) + .sync(); + + //then + assertNotNull(setUUIDMetadataResult); + assertEquals(HttpStatus.SC_OK, setUUIDMetadataResult.getStatus()); + createdUUIDMetadataList.add(setUUIDMetadataResult); + assertEquals(randomTestUUID, setUUIDMetadataResult.getData().getId()); + assertEquals(randomName, setUUIDMetadataResult.getData().getName()); + assertEquals(randomEmail, setUUIDMetadataResult.getData().getEmail()); + assertEquals(randomProfileUrl, setUUIDMetadataResult.getData().getProfileUrl()); + assertEquals(randomExternalId, setUUIDMetadataResult.getData().getExternalId()); + assertNotNull(setUUIDMetadataResult.getData().getCustom()); + } + + @Test + public void getUUIDHappyPath() throws PubNubException { + //given + final PNSetUUIDMetadataResult setUUIDMetadataResult = pubNubUnderTest.setUUIDMetadata() + .uuid(randomTestUUID) + .name(randomName) + .email(randomEmail) + .profileUrl(randomProfileUrl) + .externalId(randomExternalId) + .custom(customUUIDObject()) + .includeCustom(true) + .sync(); + createdUUIDMetadataList.add(setUUIDMetadataResult); + + //when + final PNGetUUIDMetadataResult getUUIDMetadataResult = pubNubUnderTest.getUUIDMetadata() + .uuid(randomTestUUID) + .includeCustom(true) + .sync(); + + //then + assertNotNull(getUUIDMetadataResult); + assertEquals(HttpStatus.SC_OK, getUUIDMetadataResult.getStatus()); + + assertEquals(randomTestUUID, getUUIDMetadataResult.getData().getId()); + assertEquals(setUUIDMetadataResult.getData().getName(), getUUIDMetadataResult.getData().getName()); + assertEquals(setUUIDMetadataResult.getData().getEmail(), getUUIDMetadataResult.getData().getEmail()); + assertEquals(setUUIDMetadataResult.getData().getProfileUrl(), getUUIDMetadataResult.getData().getProfileUrl()); + assertEquals(setUUIDMetadataResult.getData().getExternalId(), getUUIDMetadataResult.getData().getExternalId()); + assertNotNull(getUUIDMetadataResult.getData().getCustom()); + } + + @Test + public void getAllUUIDHappyPath() throws PubNubException { + //given + for (String testUUID: randomTestUUIDs) { + final PNSetUUIDMetadataResult setUUIDMetadataResult = pubNubUnderTest.setUUIDMetadata() + .uuid(testUUID) + .name(randomName) + .email(randomEmail) + .profileUrl(randomProfileUrl) + .externalId(randomExternalId) + .custom(customUUIDObject()) + .includeCustom(true) + .sync(); + createdUUIDMetadataList.add(setUUIDMetadataResult); + } + + //when + final PNGetAllUUIDMetadataResult getAllUUIDMetadataResult = pubNubUnderTest.getAllUUIDMetadata() + .includeCustom(true) + .includeTotalCount(true) + .limit(FETCH_LIMIT) + .sync(); + + //then + assertThat(getAllUUIDMetadataResult, allOf( + notNullValue(), + hasProperty("status", equalTo(HttpStatus.SC_OK)), + hasProperty("data", allOf( + not(empty()), + hasSize(FETCH_LIMIT))), + hasProperty("totalCount", greaterThanOrEqualTo(NUMBER_OF_RANDOM_TEST_UUIDS)), + hasProperty("next", not(isEmptyOrNullString())), + hasProperty("prev", isEmptyOrNullString()))); + } + + @Test + public void removeUUIDHappyPath() throws PubNubException { + //given + final PNSetUUIDMetadataResult setUUIDMetadataResult = pubNubUnderTest.setUUIDMetadata() + .uuid(randomTestUUID) + .name(randomName) + .email(randomEmail) + .profileUrl(randomProfileUrl) + .externalId(randomExternalId) + .custom(customUUIDObject()) + .includeCustom(true) + .sync(); + createdUUIDMetadataList.add(setUUIDMetadataResult); + + //when + final PNRemoveUUIDMetadataResult removeUUIDMetadataResult = pubNubUnderTest.removeUUIDMetadata() + .uuid(randomTestUUID) + .sync(); + + //then + assertNotNull(removeUUIDMetadataResult); + assertEquals(HttpStatus.SC_OK, removeUUIDMetadataResult.getStatus()); + } + + @After + public void cleanUp() { + createdUUIDMetadataList.forEach(pnSetUUIDMetadataResult -> { + try { + pubNubUnderTest.removeUUIDMetadata() + .uuid(pnSetUUIDMetadataResult.getData().getId()) + .sync(); + } catch (Exception e) { + LOG.warn("Could not cleanup {}", pnSetUUIDMetadataResult, e); + } + }); + } + + private Map customUUIDObject() { + return new HashMap() { + { + putIfAbsent("uuid_param1", "val1"); + putIfAbsent("uuid_param2", "val2"); + } + }; + } + + private String randomExternalId() { + return UUID.randomUUID().toString(); + } + + private String randomEmail() { + return RandomStringUtils.randomAlphabetic(6) + "@example.com"; + } + + private String randomName() { + return RandomStringUtils.randomAlphabetic(5, 10) + " " + RandomStringUtils.randomAlphabetic(5, 10); + } + + private String randomProfileUrl() { + return "http://" + RandomStringUtils.randomAlphabetic(5, 15) + ".com"; + } + + private static List randomTestUUIDs() { + final List uuids = new ArrayList<>(); + for (int i = 0; i < NUMBER_OF_RANDOM_TEST_UUIDS; i++) { + uuids.add(UUID.randomUUID().toString()); + } + return uuids; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/AccessManagerIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/pam/AccessManagerIntegrationTest.java new file mode 100644 index 000000000..10ea748ad --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/AccessManagerIntegrationTest.java @@ -0,0 +1,1088 @@ +package com.pubnub.api.integration.pam; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.endpoints.access.Grant; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.integration.util.RandomGenerator; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult; +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.awaitility.pollinterval.FibonacciPollInterval; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.pubnub.api.enums.PNStatusCategory.PNAccessDeniedCategory; +import static com.pubnub.api.enums.PNStatusCategory.PNAcknowledgmentCategory; +import static com.pubnub.api.integration.util.Utils.random; +import static com.pubnub.api.integration.util.Utils.randomChannel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +abstract class AccessManagerIntegrationTest extends BaseIntegrationTest { + + static final boolean addDelays = false; + + static final String LEVEL_APP = "subkey"; + static final String LEVEL_USER = "user"; + static final String LEVEL_CHANNEL = "channel"; + static final String LEVEL_UUID = "uuid"; + + static final int READ = 1; + static final int WRITE = 2; + static final int MANAGE = 4; + static final int DELETE = 8; + static final int GET = 16; + static final int UPDATE = 32; + static final int JOIN = 64; + + private String expectedChannel; + private String authKey; + private String uuid; + + @Override + protected void onPrePubNub() { + log.warn("onPrePubNub"); + expectedChannel = randomChannel(); + authKey = "auth_".concat(random()); + authKey = authKey.toLowerCase(); + } + + @Override + protected void onBefore() { + pubNub.getConfiguration().setIncludeInstanceIdentifier(false); + pubNub.getConfiguration().setIncludeRequestIdentifier(false); + if (performOnServer()) { + pubNub = server; + } + log.warn("performOnServer: " + performOnServer()); + + if (addDelays) { + pause(3); + } + } + + @Override + protected void onAfter() { + revokeAllAccess(); + } + + @Override + protected String provideAuthKey() { + return authKey; + } + + @Test + public void testGetPublishMessageWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(WRITE); + pubNub.publish() + .channel(expectedChannel) + .message(generatePayload()) + .async((result, status) -> { + try { + requestAccess(WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testGetPublishMessageWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.publish() + .channel(expectedChannel) + .message(generateMap()) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testPostPublishMessageWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(WRITE); + pubNub.publish() + .channel(expectedChannel) + .message(generatePayload()) + .usePOST(true) + .async((result, status) -> { + try { + requestAccess(WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testPostPublishMessageWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.publish() + .channel(expectedChannel) + .message(generateMap()) + .usePOST(true) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testMessageCountsWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ, WRITE); + pubNub.messageCounts() + .channels(Collections.singletonList(expectedChannel)) + .channelsTimetoken(Collections.singletonList(System.currentTimeMillis())) + .async((result, status) -> { + try { + requestAccess(READ, WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + + @Test + public void testMessageCountsWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.messageCounts() + .channels(Collections.singletonList(expectedChannel)) + .channelsTimetoken(Collections.singletonList(System.currentTimeMillis())) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testHistoryWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.history() + .channel(expectedChannel) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testHistoryWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.history() + .channel(expectedChannel) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testPublishHistoryWithPermission() { + final AtomicBoolean publishSuccess = new AtomicBoolean(); + final AtomicBoolean retrieveSuccess = new AtomicBoolean(); + + final JsonObject expectedMessagePayload = generatePayload(); + + requestAccess(READ); + pubNub.publish() + .channel(expectedChannel) + .message(expectedMessagePayload) + .shouldStore(true) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + publishSuccess.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(publishSuccess); + + revokeAllAccess(); + + requestAccess(WRITE); + pubNub.history() + .channel(expectedChannel) + .async((result, status) -> { + try { + requestAccess(WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + assertNotNull(result); + assertNotNull(result.getMessages()); + assertEquals(1, result.getMessages().size()); + assertEquals(expectedMessagePayload, result.getMessages().get(0).getEntry()); + retrieveSuccess.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(retrieveSuccess); + } + + @Test + public void testHereNowWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.hereNow() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testHereNowWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.hereNow() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testSetStateWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + final JsonObject expectedStatePayload = generatePayload(); + + pubNub.setPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .state(expectedStatePayload) + .async((pnSetStateResult, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testSetStateWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + final JsonObject expectedStatePayload = generatePayload(); + + requestAccess(READ); + pubNub.setPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .state(expectedStatePayload) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + assertNotNull(result); + assertEquals(expectedStatePayload, result.getState()); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testGetSetStateWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.getPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testGetStateWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.getPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testStateComboWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + final JsonObject expectedStatePayload = generatePayload(); + + requestAccess(READ); + pubNub.setPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .state(expectedStatePayload) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + assertNotNull(result); + assertEquals(expectedStatePayload, result.getState()); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + pause(2); + + pubNub.getPresenceState() + .channels(Collections.singletonList(expectedChannel)) + .uuid(pubNub.getConfiguration().getUuid()) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + assertNotNull(result); + assertEquals(expectedStatePayload, result.getStateByUUID().get(expectedChannel)); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testPresenceWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubNub, @NotNull PNStatus pnStatus) { + /*if (pnStatus.getOperation() == PNOperationType.PNSubscribeOperation && + pnStatus.getCategory() == PNConnectedCategory) { + server.subscribe() + .withPresence() + .channels(Collections.singletonList(mChannel)) + .execute(); + }*/ + } + + @Override + public void message(@NotNull PubNub pubNub, @NotNull PNMessageResult pnMessageResult) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + if ((pnPresenceEventResult.getEvent().equals("join")) + && (pnPresenceEventResult.getChannel().equals(expectedChannel))) { + if (pnPresenceEventResult.getUuid().equals(server.getConfiguration().getUuid())) { + success.set(true); + } + } + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + + }); + + subscribeToChannel(pubNub, expectedChannel); + subscribeToChannel(server, expectedChannel); + + observePam(success); + } + + @Test + public void testPublishSignalWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedPayload = RandomGenerator.newValue(5); + + requestAccess(WRITE); + pubNub.signal() + .channel(expectedChannel) + .message(expectedPayload) + .async((result, status) -> { + try { + requestAccess(WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testPublishSignalWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + final String expectedPayload = RandomGenerator.newValue(5); + + pubNub.signal() + .channel(expectedChannel) + .message(expectedPayload) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testDeleteMessageWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(DELETE); + pubNub.deleteMessages() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + requestAccess(DELETE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testDeleteMessageWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.deleteMessages() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + + @Test + public void testFetchMessagesWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testFetchMessagesWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testFetchMessageActionsWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .includeMessageActions(true) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testFetchMessageActionsWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.fetchMessages() + .channels(Collections.singletonList(expectedChannel)) + .includeMessageActions(true) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testAddAMessageActionWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(WRITE); + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(1L)) + .async((result, status) -> { + try { + requestAccess(WRITE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testAddMessageActionWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(1L)) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testGetMessageActionsWithPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(READ); + pubNub.getMessageActions() + .channel(expectedChannel) + .async((result, status) -> { + try { + requestAccess(READ); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testGetMessageActionsWithoutPermission() { + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.getMessageActions() + .channel(expectedChannel) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testRemoveMessageActionWithPermission() throws PubNubException { + final AtomicBoolean success = new AtomicBoolean(); + + requestAccess(WRITE); + final PNAddMessageActionResult addMessageActionResult = pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(1L)) + .sync(); + assertNotNull(addMessageActionResult); + + revokeAllAccess(); + + requestAccess(DELETE); + pubNub.removeMessageAction() + .channel(expectedChannel) + .messageTimetoken(addMessageActionResult.getMessageTimetoken()) + .actionTimetoken(addMessageActionResult.getActionTimetoken()) + .async((result, status) -> { + try { + requestAccess(DELETE); + assertAuthKey(status); + assertUuid(status); + assertStatusSuccess(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + @Test + public void testRemoveMessageActionWithoutPermission() throws PubNubException { + requestAccess(WRITE); + + pause(TIMEOUT_MEDIUM); + + PNPublishResult publishResult = pubNub.publish() + .channel(expectedChannel) + .message(random()) + .shouldStore(true) + .sync(); + + assertNotNull(publishResult); + PNAddMessageActionResult addMessageActionResult = pubNub.addMessageAction() + .channel(expectedChannel) + .messageAction(new PNMessageAction() + .setType("reaction") + .setValue(RandomGenerator.emoji()) + .setMessageTimetoken(publishResult.getTimetoken())) + .sync(); + + revokeAllAccess(); + + final AtomicBoolean success = new AtomicBoolean(); + + assertNotNull(addMessageActionResult); + pubNub.removeMessageAction() + .channel(expectedChannel) + .messageTimetoken(addMessageActionResult.getMessageTimetoken()) + .actionTimetoken(addMessageActionResult.getActionTimetoken()) + .async((result, status) -> { + try { + assertAuthKey(status); + assertUuid(status); + assertStatusError(status); + assertCategory(status); + success.set(true); + } catch (AssertionError | Exception e) { + e.printStackTrace(); + retry(status); + } + }); + + observePam(success); + } + + private void revokeAllAccess() { + requestAccess(); + } + + private void requestAccess(Integer... bitmasks) { + int sum = 0; + for (Integer bitmask : bitmasks) { + sum += bitmask; + } + boolean revokeAllAccess = sum == 0; + + if (performOnServer()) { + return; + } + + if (!revokeAllAccess) { + log.info(String.format("Requesting access for %1$s at %2$s level", sum, getPamLevel())); + } else { + log.info("Revoking all access!"); + } + + final List bitList = Arrays.asList(bitmasks); + + final Grant grantOperationBuilder = getServer().grant() + .read(bitList.contains(READ)) + .write(bitList.contains(WRITE)) + .manage(bitList.contains(MANAGE)) + .delete(bitList.contains(DELETE)) + .get(bitList.contains(GET)) + .update(bitList.contains(UPDATE)) + .join(bitList.contains(JOIN)) + .ttl(1); + + if (!revokeAllAccess) { + switch (getPamLevel()) { + case LEVEL_USER: + grantOperationBuilder.authKeys(Collections.singletonList(authKey)); + grantOperationBuilder.channels(Arrays.asList(expectedChannel, expectedChannel.concat("-pnpres"))); + break; + case LEVEL_CHANNEL: + grantOperationBuilder.channels(Arrays.asList(expectedChannel, expectedChannel.concat("-pnpres"))); + break; + } + } + + try { + final PNAccessManagerGrantResult grantResult = grantOperationBuilder.sync(); + assertNotNull(grantResult); + log.info(String.format("Access request result: %s", new Gson().toJson(grantResult))); + if (revokeAllAccess) { + assertEquals(LEVEL_APP, grantResult.getLevel()); + } else { + assertEquals(getPamLevel(), grantResult.getLevel()); + if (!getPamLevel().equals(LEVEL_APP)) { + assertTrue(grantResult.getChannels().containsKey(expectedChannel)); + assertTrue(grantResult.getChannels().containsKey(expectedChannel.concat("-pnpres"))); + } + } + } catch (PubNubException e) { + e.printStackTrace(); + } + + if (addDelays) { + pause(2); + } + } + + private void observePam(final AtomicBoolean success) { + Awaitility.await() + .atMost(Durations.TEN_SECONDS) + .pollInterval(new FibonacciPollInterval(TimeUnit.SECONDS)) + .untilTrue(success); + } + + + private void assertAuthKey(PNStatus status) throws AssertionError { + if (!performOnServer()) { + assertEquals(authKey, status.getAuthKey()); + } + } + + private void assertStatusError(PNStatus status) throws AssertionError { + if (!performOnServer()) { + assertTrue(status.isError()); + } else { + assertFalse(status.isError()); + } + } + + private void assertStatusSuccess(PNStatus status) throws AssertionError { + if (!performOnServer()) { + assertFalse(status.isError()); + } else { + assertFalse(status.isError()); + } + } + + private void assertCategory(PNStatus status) throws AssertionError { + if (!performOnServer()) { + assertEquals(PNAccessDeniedCategory, status.getCategory()); + } else { + assertEquals(PNAcknowledgmentCategory, status.getCategory()); + } + } + + private void assertUuid(PNStatus pnStatus) throws AssertionError { + assertEquals(pubNub.getConfiguration().getUuid(), pnStatus.getUuid()); + } + + abstract String getPamLevel(); + + boolean performOnServer() { + return false; + } + + private void retry(PNStatus pnStatus) { + // this causes OOMs + // pnStatus.retry(); + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/GrantIT.java b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantIT.java new file mode 100644 index 000000000..d9de841d1 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantIT.java @@ -0,0 +1,50 @@ +package com.pubnub.api.integration.pam; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; + +public class GrantIT extends BaseIntegrationTest { + + @Test + public void grantAllForUUID() throws PubNubException { + String uuid = "uuid123"; + String authKey = "authKey123"; + int ttl = 120; + + PNAccessManagerGrantResult expectedResult = PNAccessManagerGrantResult.builder() + .channelGroups(Collections.emptyMap()) + .channels(Collections.emptyMap()) + .level("uuid") + .ttl(ttl) + .subscribeKey(getServer().getConfiguration().getSubscribeKey()) + .uuids(Collections.singletonMap(uuid, Collections.singletonMap(authKey, PNAccessManagerKeyData.builder() + .getEnabled(true) + .deleteEnabled(true) + .updateEnabled(true) + .readEnabled(false) + .manageEnabled(false) + .joinEnabled(false) + .writeEnabled(false) + .build()))) + .build(); + + PNAccessManagerGrantResult result = getServer() + .grant() + .uuids(Collections.singletonList(uuid)) + .authKeys(Collections.singletonList(authKey)) + .ttl(ttl) + .get(true) + .update(true) + .delete(true) + .sync(); + System.out.println(result); + assertEquals(expectedResult, result); + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java new file mode 100644 index 000000000..586a33558 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java @@ -0,0 +1,55 @@ +package com.pubnub.api.integration.pam; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.enums.PNLogVerbosity; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import org.junit.Test; + +import java.util.Arrays; +import static org.junit.Assert.assertEquals; + + +public class GrantTokenIT extends BaseIntegrationTest { + private final PubNub pubNubUnderTest = getServer(); + + @Test + public void happyPath() throws PubNubException { + //given + pubNubUnderTest.getConfiguration().setLogVerbosity(PNLogVerbosity.BODY); + final int expectedTTL = 1337; + final String expectedChannelResourceName = "channelResource"; + final String expectedChannelPattern = "channel.*"; + final String expectedChannelGroupResourceId = "channelGroup"; + final String expectedChannelGroupPattern = "channelGroup.*"; + + //when + final PNGrantTokenResult grantTokenResponse = pubNubUnderTest + .grantToken() + .ttl(expectedTTL) + .channels(Arrays.asList(ChannelGrant.name(expectedChannelResourceName).delete(), + ChannelGrant.pattern(expectedChannelPattern).write())) + .channelGroups(Arrays.asList(ChannelGroupGrant.id(expectedChannelGroupResourceId).read(), + ChannelGroupGrant.pattern(expectedChannelGroupPattern).manage())) + .sync(); + + final PNToken pnToken = pubNubUnderTest.parseToken(grantTokenResponse.getToken()); + + //then + assertEquals(expectedTTL, pnToken.getTtl()); + assertEquals(new PNToken.PNResourcePermissions(false, false, false, false, false, false, false), + pnToken.getResources().getChannels().get(expectedChannelResourceName)); + assertEquals(new PNToken.PNResourcePermissions(false, true, false, false, false, false, false), + pnToken.getResources().getChannelGroups().get(expectedChannelGroupResourceId)); + assertEquals(new PNToken.PNResourcePermissions(false, false, true, false, false, false, false), + pnToken.getPatterns().getChannels().get(expectedChannelPattern)); + assertEquals(new PNToken.PNResourcePermissions(false, false, false, true, false, false, false), + pnToken.getPatterns().getChannelGroups().get(expectedChannelGroupPattern)); + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/PamChannelIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/pam/PamChannelIntegrationTest.java new file mode 100644 index 000000000..0ce08e973 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/PamChannelIntegrationTest.java @@ -0,0 +1,9 @@ +package com.pubnub.api.integration.pam; + +public class PamChannelIntegrationTest extends AccessManagerIntegrationTest { + + @Override + public String getPamLevel() { + return LEVEL_CHANNEL; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/PamServerIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/pam/PamServerIntegrationTest.java new file mode 100644 index 000000000..37ae15c49 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/PamServerIntegrationTest.java @@ -0,0 +1,14 @@ +package com.pubnub.api.integration.pam; + +public class PamServerIntegrationTest extends AccessManagerIntegrationTest { + + @Override + public String getPamLevel() { + return LEVEL_APP; + } + + @Override + boolean performOnServer() { + return true; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/PamSubkeyIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/pam/PamSubkeyIntegrationTest.java new file mode 100644 index 000000000..551bdf589 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/PamSubkeyIntegrationTest.java @@ -0,0 +1,9 @@ +package com.pubnub.api.integration.pam; + +public class PamSubkeyIntegrationTest extends AccessManagerIntegrationTest { + + @Override + public String getPamLevel() { + return LEVEL_APP; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/PamUserIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/pam/PamUserIntegrationTest.java new file mode 100644 index 000000000..64c6a5d99 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/PamUserIntegrationTest.java @@ -0,0 +1,9 @@ +package com.pubnub.api.integration.pam; + +public class PamUserIntegrationTest extends AccessManagerIntegrationTest { + + @Override + public String getPamLevel() { + return LEVEL_USER; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/util/BaseIntegrationTest.java b/src/integrationTest/java/com/pubnub/api/integration/util/BaseIntegrationTest.java new file mode 100644 index 000000000..a26377785 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/util/BaseIntegrationTest.java @@ -0,0 +1,373 @@ +package com.pubnub.api.integration.util; + +import com.google.gson.JsonObject; +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubError; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.enums.PNLogVerbosity; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import okhttp3.logging.HttpLoggingInterceptor; +import org.aeonbits.owner.ConfigFactory; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.awaitility.pollinterval.FibonacciPollInterval; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertEquals; + +public abstract class BaseIntegrationTest { + + protected Logger log = LoggerFactory.getLogger(BaseIntegrationTest.class); + + private static String SUB_KEY; + private static String PUB_KEY; + private static String PAM_SUB_KEY; + private static String PAM_PUB_KEY; + private static String PAM_SEC_KEY; + + public PubNub pubNub; + public PubNub server; + + public int TIMEOUT_MEDIUM = 5; + public int TIMEOUT_LOW = 2; + + private List mGuestClients = new ArrayList<>(); + + @BeforeClass + public static void onlyOnce() { + final ITTestConfig itTestConfig = ConfigFactory.create(ITTestConfig.class, System.getenv()); + SUB_KEY = itTestConfig.subscribeKey(); + PUB_KEY = itTestConfig.publishKey(); + PAM_SUB_KEY = itTestConfig.pamSubKey(); + PAM_PUB_KEY = itTestConfig.pamPubKey(); + PAM_SEC_KEY = itTestConfig.pamSecKey(); + } + + @Before + public void before() { + onPrePubNub(); + pubNub = getPubNub(); + if (needsServer()) { + server = getServer(); + } + onBefore(); + } + + @After + public void after() { + onAfter(); + destroyClient(pubNub); + if (mGuestClients != null) { + for (PubNub guestClient : mGuestClients) { + destroyClient(guestClient); + } + } + // properties.clear(); + } + + public PubNub getPubNub() { + PNConfiguration pnConfiguration = provideStagingConfiguration(); + if (pnConfiguration == null) { + pnConfiguration = getBasicPnConfiguration(); + } + final PubNub pubNub = new PubNub(pnConfiguration); + registerGuestClient(pubNub); + return pubNub; + } + + protected PubNub getServer() { + final PubNub pubNub = new PubNub(getServerPnConfiguration()); + registerGuestClient(pubNub); + return pubNub; + } + + public PubNub getPubNub(PNConfiguration pnConfiguration) { + final PubNub pubNub = new PubNub(pnConfiguration); + registerGuestClient(pubNub); + return pubNub; + } + + private void registerGuestClient(PubNub guestClient) { + if (mGuestClients == null) { + mGuestClients = new ArrayList<>(); + } + mGuestClients.add(guestClient); + } + + protected void destroyClient(PubNub client) { + client.unsubscribeAll(); + client.forceDestroy(); + } + + protected PNConfiguration getBasicPnConfiguration() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + if (!needsServer()) { + pnConfiguration.setSubscribeKey(SUB_KEY); + pnConfiguration.setPublishKey(PUB_KEY); + } else { + pnConfiguration.setSubscribeKey(PAM_SUB_KEY); + pnConfiguration.setPublishKey(PAM_PUB_KEY); + pnConfiguration.setAuthKey(provideAuthKey()); + } + pnConfiguration.setLogVerbosity(PNLogVerbosity.NONE); + pnConfiguration.setHttpLoggingInterceptor(createInterceptor()); + pnConfiguration.setUuid("client-".concat(UUID.randomUUID().toString())); + return pnConfiguration; + } + + private PNConfiguration getServerPnConfiguration() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(PAM_SUB_KEY); + pnConfiguration.setPublishKey(PAM_PUB_KEY); + pnConfiguration.setSecretKey(PAM_SEC_KEY); + pnConfiguration.setLogVerbosity(PNLogVerbosity.NONE); + pnConfiguration.setHttpLoggingInterceptor(createInterceptor()); + pnConfiguration.setUuid("server-".concat(UUID.randomUUID().toString())); + return pnConfiguration; + } + + private HttpLoggingInterceptor createInterceptor() { + final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(log::debug); + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + return interceptor; + } + + protected void subscribeToChannel(@NotNull PubNub pubnub, @NotNull String... channels) { + pubnub.subscribe() + .channels(Arrays.asList(channels)) + .withPresence() + .execute(); + pause(1); + } + + protected void subscribeToChannel(final PubNub pubnub, final List channels) { + + final AtomicBoolean subscribeSuccess = new AtomicBoolean(); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + assert status.getAffectedChannels() != null; + if (status.getAffectedChannels().containsAll(channels)) { + subscribeSuccess.set(true); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubNub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull final PubNub pubnub, @NotNull final PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull final PubNub pubnub, @NotNull final PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull final PubNub pubnub, @NotNull final PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnActionResult) { + + } + + }); + + pubnub.subscribe() + .channels(channels) + .withPresence() + .execute(); + + Awaitility.await().atMost(Durations.TEN_SECONDS).untilTrue(subscribeSuccess); + } + + protected void subscribeToChannelGroup(@NotNull PubNub pubnub, @NotNull String group) { + pubnub.subscribe() + .channelGroups(Collections.singletonList(group)) + .withPresence() + .execute(); + pause(1); + } + + protected void unsubscribeFromChannel(PubNub pubNub, String channel) { + pubNub.unsubscribe() + .channels(Collections.singletonList(channel)) + .execute(); + pause(1); + } + + protected void unsubscribeFromAllChannels(PubNub pubNub) { + pubNub.unsubscribeAll(); + pause(1); + } + + protected Map generateMessage(PubNub pubNub, String message) { + final Map map = new HashMap<>(); + map.put("publisher", pubNub.getConfiguration().getUuid()); + map.put("text", "mymsg" + RandomGenerator.newValue(5) + "+" + RandomGenerator.newValue(5)); + map.put("uncd", RandomGenerator.unicode(8)); + map.put("extra", message); + return map; + } + + protected JsonObject generateMessage(PubNub pubNub) { + final JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("publisher", pubNub.getConfiguration().getUuid()); + jsonObject.addProperty("text", RandomGenerator.newValue(8)); + jsonObject.addProperty("uncd", RandomGenerator.unicode(8)); + return jsonObject; + } + + protected JsonObject generatePayload() { + final JsonObject state = new JsonObject(); + state.addProperty("text", RandomGenerator.newValue(10)); + state.addProperty("uncd", RandomGenerator.unicode(8)); + state.addProperty("info", RandomGenerator.newValue(8)); + return state; + } + + protected JSONObject generatePayloadJSON() { + final JSONObject state = new JSONObject(); + try { + state.put("text", RandomGenerator.newValue(10)); + state.put("uncd", RandomGenerator.unicode(8)); + state.put("info", RandomGenerator.newValue(8)); + } catch (JSONException e) { + e.printStackTrace(); + } + return state; + } + + protected HashMap generateMap() { + final HashMap map = new HashMap<>(); + map.put("text", RandomGenerator.newValue(10)); + map.put("uncd", RandomGenerator.unicode(8)); + map.put("info", RandomGenerator.newValue(8)); + return map; + } + + protected void publishMessage(PubNub pubNub, String channel, String message) { + pubNub.publish() + .message(generateMessage(pubNub, message)) + .channel(channel) + .shouldStore(true) + .async((result, status) -> { + + }); + } + + protected void publishMessage(PubNub pubNub, String channel, String message, Map meta) { + pubNub.publish() + .message(generateMessage(pubNub, message)) + .channel(channel) + .meta(meta) + .shouldStore(true) + .async((result, status) -> { + + }); + } + + protected void pause(int seconds) { + try { + Thread.sleep(seconds * 1_000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private boolean needsServer() { + return provideAuthKey() != null; + } + + protected void listen(AtomicBoolean success) { + Awaitility.await() + .atMost(Durations.FIVE_SECONDS) + .with() + .untilTrue(success); + } + + protected void listen(AtomicBoolean success, Callable callable) { + Awaitility.await() + .atMost(Durations.TEN_MINUTES) + .pollInterval(new FibonacciPollInterval(TimeUnit.SECONDS)) + .with() + .until(callable); + } + + protected void assertException(PubNubError pubNubError, PubNubException e) { + assertEquals(pubNubError, e.getPubnubError()); + } + + protected void onBefore() { + + } + + protected void onAfter() { + + } + + protected void onPrePubNub() { + + } + + protected String provideAuthKey() { + return null; + } + + protected PNConfiguration provideStagingConfiguration() { + return null; + } + +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/util/ITTestConfig.java b/src/integrationTest/java/com/pubnub/api/integration/util/ITTestConfig.java new file mode 100644 index 000000000..844c956da --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/util/ITTestConfig.java @@ -0,0 +1,22 @@ +package com.pubnub.api.integration.util; + +import org.aeonbits.owner.Config; + +@Config.Sources({"file:test.properties"}) +public interface ITTestConfig extends Config { + + @Config.Key("subKey") + String subscribeKey(); + + @Config.Key("pubKey") + String publishKey(); + + @Config.Key("pamSubKey") + String pamSubKey(); + + @Config.Key("pamPubKey") + String pamPubKey(); + + @Config.Key("pamSecKey") + String pamSecKey(); +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/util/RandomGenerator.java b/src/integrationTest/java/com/pubnub/api/integration/util/RandomGenerator.java new file mode 100644 index 000000000..1f39b9df6 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/util/RandomGenerator.java @@ -0,0 +1,165 @@ +package com.pubnub.api.integration.util; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class RandomGenerator { + + private static final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static String newValue(int length) { + final Random random = new SecureRandom(); + if (length <= 0) { + throw new IllegalArgumentException("String length must be a positive integer"); + } + + final StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append(characters.charAt(random.nextInt(characters.length()))); + } + + return sb.toString(); + } + + public static String emoji() { + final Random random = new SecureRandom(); + final List emojiSet = new ArrayList<>(); + emojiSet.add("😀"); + emojiSet.add("😁"); + emojiSet.add("😂"); + emojiSet.add("🤣"); + emojiSet.add("😃"); + emojiSet.add("😄"); + emojiSet.add("😅"); + emojiSet.add("😆"); + emojiSet.add("😉"); + emojiSet.add("😊"); + emojiSet.add("😋"); + emojiSet.add("😎"); + emojiSet.add("😍"); + emojiSet.add("😘"); + emojiSet.add("🥰"); + emojiSet.add("😗"); + emojiSet.add("😙"); + emojiSet.add("😚"); + emojiSet.add("☺️"); + emojiSet.add("🙂"); + emojiSet.add("🤗"); + emojiSet.add("🤩"); + emojiSet.add("🤔"); + emojiSet.add("🤨"); + emojiSet.add("😐"); + emojiSet.add("😑"); + emojiSet.add("😶"); + emojiSet.add("🙄"); + emojiSet.add("😏"); + emojiSet.add("😣"); + emojiSet.add("😥"); + emojiSet.add("😮"); + emojiSet.add("🤐"); + emojiSet.add("😯"); + emojiSet.add("😪"); + emojiSet.add("😫"); + emojiSet.add("😴"); + emojiSet.add("😌"); + emojiSet.add("😛"); + emojiSet.add("😜"); + emojiSet.add("😝"); + emojiSet.add("🤤"); + emojiSet.add("😒"); + emojiSet.add("😓"); + emojiSet.add("😔"); + emojiSet.add("😕"); + emojiSet.add("🙃"); + emojiSet.add("🤑"); + emojiSet.add("😲"); + emojiSet.add("☹️"); + emojiSet.add("🙁"); + emojiSet.add("😖"); + emojiSet.add("😞"); + emojiSet.add("😟"); + emojiSet.add("😤"); + emojiSet.add("😢"); + emojiSet.add("😭"); + emojiSet.add("😦"); + emojiSet.add("😧"); + emojiSet.add("😨"); + emojiSet.add("😩"); + emojiSet.add("🤯"); + emojiSet.add("😬"); + emojiSet.add("😰"); + emojiSet.add("😱"); + emojiSet.add("🥵"); + emojiSet.add("🥶"); + emojiSet.add("😳"); + emojiSet.add("🤪"); + emojiSet.add("😵"); + emojiSet.add("😡"); + emojiSet.add("😠"); + emojiSet.add("🤬"); + emojiSet.add("😷"); + emojiSet.add("🤒"); + emojiSet.add("🤕"); + emojiSet.add("🤢"); + emojiSet.add("🤮"); + emojiSet.add("🤧"); + emojiSet.add("😇"); + emojiSet.add("🤠"); + emojiSet.add("🤡"); + emojiSet.add("🥳"); + emojiSet.add("🥴"); + emojiSet.add("🥺"); + emojiSet.add("🤥"); + emojiSet.add("🤫"); + emojiSet.add("🤭"); + emojiSet.add("🧐"); + emojiSet.add("🤓"); + emojiSet.add("😈"); + emojiSet.add("👿"); + emojiSet.add("👹"); + emojiSet.add("👺"); + emojiSet.add("💀"); + emojiSet.add("👻"); + emojiSet.add("👽"); + emojiSet.add("🤖"); + emojiSet.add("💩"); + emojiSet.add("😺"); + emojiSet.add("😸"); + emojiSet.add("😹"); + emojiSet.add("😻"); + emojiSet.add("😼"); + emojiSet.add("😽"); + emojiSet.add("🙀"); + emojiSet.add("😿"); + emojiSet.add("😾"); + return emojiSet.get(random.nextInt(emojiSet.size())) + ""; + } + + public static String unicode(int length) { + final String unicodeChars = "!?+-="; + + final Random random = new SecureRandom(); + + if (length <= 0) { + length = unicodeChars.length(); + } + + final StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append(unicodeChars.charAt(random.nextInt(unicodeChars.length()))); + } + + return sb.toString(); + } + + public static String get() { + return newValue(5).concat(unicode(5)).concat(newValue(5)); + } + + public static int randomNumber(int min, int max) { + final Random r = new Random(); + return r.nextInt((max - min) + 1) + min; + } +} diff --git a/src/integrationTest/java/com/pubnub/api/integration/util/Utils.java b/src/integrationTest/java/com/pubnub/api/integration/util/Utils.java new file mode 100644 index 000000000..c32aed699 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/util/Utils.java @@ -0,0 +1,98 @@ +package com.pubnub.api.integration.util; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.pubsub.Publish; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import okhttp3.Request; + +import java.text.SimpleDateFormat; +import java.util.*; + +public class Utils { + + private Utils() { + } + + public static String parseDate(Long timetoken) { + return new SimpleDateFormat("HH:mm:ss:SSS").format(timetoken / 10_000L); + } + + public static boolean isSorted(List list) { + final List sorted = new ArrayList() {{ + addAll(list); + }}; + Collections.sort(sorted); + + final List reversed = new ArrayList() {{ + addAll(list); + }}; + Collections.sort(reversed); + Collections.reverse(reversed); + + return list.equals(sorted) || list.equals(reversed); + } + + public static PNPublishResult publish(PubNub pubnub, String channel, int indicator) { + try { + return pubnub.publish() + .channel(channel) + .message(indicator + "_" + randomChannel()) + .meta("Metata_".concat(String.valueOf(indicator))) + .shouldStore(true) + .sync(); + } catch (PubNubException e) { + return null; + } + } + + public static String random() { + return RandomGenerator.newValue(10); + } + + public static String randomChannel() { + return "ch_".concat(RandomGenerator.newValue(10)).toLowerCase(); + } + + public static String queryParam(PNStatus pnStatus, String param) { + final Request request = (Request) pnStatus.getClientRequest(); + return request.url().queryParameter(param); + } + + public static List publishMixed(PubNub pubnub, int count, String channel) { + final List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + final Publish publishBuilder = pubnub.publish() + .channel(channel) + .message(String.valueOf(i).concat("_msg")) + .shouldStore(true); + if (i % 2 == 0) { + publishBuilder.meta(generateMap()); + } else if (i % 3 == 0) { + publishBuilder.meta(RandomGenerator.newValue(4)); + } else { + publishBuilder.meta(null); + } + + PNPublishResult pnPublishResult = null; + try { + pnPublishResult = publishBuilder.sync(); + } catch (PubNubException e) { + e.printStackTrace(); + } + + list.add(pnPublishResult); + + } + return list; + } + + public static Map generateMap() { + final HashMap map = new HashMap<>(); + map.put("text", RandomGenerator.newValue(8)); + map.put("uncd", RandomGenerator.unicode(6)); + map.put("info", RandomGenerator.newValue(8)); + return map; + } +} diff --git a/src/main/java/com/pubnub/api/PNConfiguration.java b/src/main/java/com/pubnub/api/PNConfiguration.java new file mode 100644 index 000000000..f31737596 --- /dev/null +++ b/src/main/java/com/pubnub/api/PNConfiguration.java @@ -0,0 +1,279 @@ +package com.pubnub.api; + + +import com.pubnub.api.enums.PNHeartbeatNotificationOptions; +import com.pubnub.api.enums.PNLogVerbosity; +import com.pubnub.api.enums.PNReconnectionPolicy; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.extern.java.Log; +import okhttp3.Authenticator; +import okhttp3.CertificatePinner; +import okhttp3.ConnectionSpec; +import okhttp3.logging.HttpLoggingInterceptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509ExtendedTrustManager; +import java.net.Proxy; +import java.net.ProxySelector; +import java.util.UUID; + +@Getter +@Setter +@Accessors(chain = true) + +@Log +public class PNConfiguration { + private static final int DEFAULT_DEDUPE_SIZE = 100; + private static final int PRESENCE_TIMEOUT = 300; + private static final int MINIMUM_PRESENCE_TIMEOUT = 20; + private static final int NON_SUBSCRIBE_REQUEST_TIMEOUT = 10; + private static final int SUBSCRIBE_TIMEOUT = 310; + private static final int CONNECT_TIMEOUT = 5; + private static final int FILE_MESSAGE_PUBLISH_RETRY_LIMIT = 5; + + @Getter + private SSLSocketFactory sslSocketFactory; + + @Getter + private X509ExtendedTrustManager x509ExtendedTrustManager; + + @Getter + private ConnectionSpec connectionSpec; + + @Getter + private HostnameVerifier hostnameVerifier; + + /** + * Set to true to send a UUID for PubNub instance + */ + @Getter + private boolean includeInstanceIdentifier; + + /** + * Set to true to send a UUID on each request + */ + @Getter + private boolean includeRequestIdentifier; + + /** + * By default, the origin is pointing directly to PubNub servers. If a proxy origin is needed, set a custom + * origin using this parameter. + */ + private String origin; + private int subscribeTimeout; + + + /** + * In seconds, how long the server will consider this client to be online before issuing a leave event. + */ + @Setter(AccessLevel.NONE) + private int presenceTimeout; + /** + * In seconds, How often the client should announce it's existence via heartbeating. + */ + @Setter(AccessLevel.NONE) + private int heartbeatInterval; + + /** + * set to true to switch the client to HTTPS:// based communications. + */ + private boolean secure; + /** + * Subscribe Key provided by PubNub + */ + private String subscribeKey; + /** + * Publish Key provided by PubNub. + */ + private String publishKey; + private String secretKey; + private String cipherKey; + private String authKey; + private String uuid; + /** + * If proxies are forcefully caching requests, set to true to allow the client to randomize the subdomain. + * This configuration is not supported if custom origin is enabled. + */ + @Deprecated + private boolean cacheBusting; + + /** + * toggle to enable verbose logging. + */ + + @NotNull + private PNLogVerbosity logVerbosity; + + /** + * Stores the maximum number of seconds which the client should wait for connection before timing out. + */ + private int connectTimeout; + + /** + * Reference on number of seconds which is used by client during non-subscription operations to + * check whether response potentially failed with 'timeout' or not. + */ + private int nonSubscribeRequestTimeout; + + /** + * Suppress leave events when a channel gets disconnected + */ + private boolean suppressLeaveEvents; + + /** + * verbosity of heartbeat configuration, by default only alerts on failed heartbeats + */ + @Nullable + private PNHeartbeatNotificationOptions heartbeatNotificationOptions; + + /** + * filterExpression used as part of PSV2 specification. + */ + @Setter + private String filterExpression; + + + /** + * Reconnection policy which will be used if/when networking goes down + */ + @Setter + @Nullable + private PNReconnectionPolicy reconnectionPolicy; + + /** + * Set how many times the reconneciton manager will try to connect before giving app + */ + @Setter + private int maximumReconnectionRetries; + + /** + * Proxy configuration which will be passed to the networking layer. + */ + @Setter + private Proxy proxy; + @Setter + private ProxySelector proxySelector; + @Setter + private Authenticator proxyAuthenticator; + + @Setter + private CertificatePinner certificatePinner; + + @Setter + private Integer maximumConnections; + + @Setter + private HttpLoggingInterceptor httpLoggingInterceptor; + + /** + * if set, the SDK will alert once the number of messages arrived in one call equal to the threshold + */ + private Integer requestMessageCountThreshold; + + /** + * Use Google App Engine based networking configuration + */ + @Setter + private boolean googleAppEngineNetworking; + @Setter + private boolean startSubscriberThread; + + @Setter + private boolean dedupOnSubscribe; + @Setter + private Integer maximumMessagesCacheSize; + @Setter + private boolean useRandomInitializationVector; + + @Setter + private int fileMessagePublishRetryLimit; + + /** + * Enables explicit presence control. + * When set to true heartbeat calls will contain only channels and groups added explicitly + * using {@link PubNub#presence()}. Should be used only with ACL set on the server side. + * For more information please contact PubNub support + * + * @see PubNub#presence() + * @see PNConfiguration#heartbeatInterval + */ + @Deprecated + @Setter + private boolean managePresenceListManually; + /** + * Initialize the PNConfiguration with default values + */ + public PNConfiguration() { + setPresenceTimeoutWithCustomInterval(PRESENCE_TIMEOUT, 0); + + uuid = "pn-" + UUID.randomUUID().toString(); + + nonSubscribeRequestTimeout = NON_SUBSCRIBE_REQUEST_TIMEOUT; + subscribeTimeout = SUBSCRIBE_TIMEOUT; + connectTimeout = CONNECT_TIMEOUT; + + logVerbosity = PNLogVerbosity.NONE; + + heartbeatNotificationOptions = PNHeartbeatNotificationOptions.FAILURES; + reconnectionPolicy = PNReconnectionPolicy.NONE; + + secure = true; + + includeInstanceIdentifier = false; + + includeRequestIdentifier = true; + + startSubscriberThread = true; + + maximumReconnectionRetries = -1; + + dedupOnSubscribe = false; + suppressLeaveEvents = false; + maximumMessagesCacheSize = DEFAULT_DEDUPE_SIZE; + useRandomInitializationVector = true; + fileMessagePublishRetryLimit = FILE_MESSAGE_PUBLISH_RETRY_LIMIT; + managePresenceListManually = false; + } + + /** + * set presence configurations for timeout and announce interval. + * + * @param timeout presence timeout; how long before the server considers this client to be gone. + * @param interval presence announce interval, how often the client should announce itself. + * @return returns itself. + */ + public PNConfiguration setPresenceTimeoutWithCustomInterval(int timeout, int interval) { + timeout = validatePresenceTimeout(timeout); + this.presenceTimeout = timeout; + this.heartbeatInterval = interval; + + return this; + } + + /** + * set presence configurations for timeout and allow the client to pick the best interval + * + * @param timeout presence timeout; how long before the server considers this client to be gone. + * @return returns itself. + */ + public PNConfiguration setPresenceTimeout(int timeout) { + timeout = validatePresenceTimeout(timeout); + return setPresenceTimeoutWithCustomInterval(timeout, (timeout / 2) - 1); + } + + private int validatePresenceTimeout(int timeout) { + int validTimeout = timeout; + if (timeout < MINIMUM_PRESENCE_TIMEOUT) { + validTimeout = MINIMUM_PRESENCE_TIMEOUT; + log.warning("Presence timeout is too low. Defaulting to: " + MINIMUM_PRESENCE_TIMEOUT); + } + return validTimeout; + } + +} diff --git a/src/main/java/com/pubnub/api/PubNub.java b/src/main/java/com/pubnub/api/PubNub.java new file mode 100644 index 000000000..58bc2536e --- /dev/null +++ b/src/main/java/com/pubnub/api/PubNub.java @@ -0,0 +1,606 @@ +package com.pubnub.api; + +import com.pubnub.api.builder.PresenceBuilder; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.builder.SubscribeBuilder; +import com.pubnub.api.builder.UnsubscribeBuilder; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.endpoints.DeleteMessages; +import com.pubnub.api.endpoints.FetchMessages; +import com.pubnub.api.endpoints.History; +import com.pubnub.api.endpoints.MessageCounts; +import com.pubnub.api.endpoints.Time; +import com.pubnub.api.endpoints.access.Grant; +import com.pubnub.api.endpoints.access.GrantToken; +import com.pubnub.api.endpoints.channel_groups.AddChannelChannelGroup; +import com.pubnub.api.endpoints.channel_groups.AllChannelsChannelGroup; +import com.pubnub.api.endpoints.channel_groups.DeleteChannelGroup; +import com.pubnub.api.endpoints.channel_groups.ListAllChannelGroup; +import com.pubnub.api.endpoints.channel_groups.RemoveChannelChannelGroup; +import com.pubnub.api.endpoints.files.DeleteFile; +import com.pubnub.api.endpoints.files.DownloadFile; +import com.pubnub.api.endpoints.files.GetFileUrl; +import com.pubnub.api.endpoints.files.ListFiles; +import com.pubnub.api.endpoints.files.PublishFileMessage; +import com.pubnub.api.endpoints.files.SendFile; +import com.pubnub.api.endpoints.message_actions.AddMessageAction; +import com.pubnub.api.endpoints.message_actions.GetMessageActions; +import com.pubnub.api.endpoints.message_actions.RemoveMessageAction; +import com.pubnub.api.endpoints.objects_api.channel.GetAllChannelsMetadata; +import com.pubnub.api.endpoints.objects_api.channel.GetChannelMetadata; +import com.pubnub.api.endpoints.objects_api.channel.RemoveChannelMetadata; +import com.pubnub.api.endpoints.objects_api.channel.SetChannelMetadata; +import com.pubnub.api.endpoints.objects_api.members.GetChannelMembers; +import com.pubnub.api.endpoints.objects_api.members.ManageChannelMembers; +import com.pubnub.api.endpoints.objects_api.members.RemoveChannelMembers; +import com.pubnub.api.endpoints.objects_api.members.SetChannelMembers; +import com.pubnub.api.endpoints.objects_api.memberships.GetMemberships; +import com.pubnub.api.endpoints.objects_api.memberships.ManageMemberships; +import com.pubnub.api.endpoints.objects_api.memberships.RemoveMemberships; +import com.pubnub.api.endpoints.objects_api.memberships.SetMemberships; +import com.pubnub.api.endpoints.objects_api.uuid.GetAllUUIDMetadata; +import com.pubnub.api.endpoints.objects_api.uuid.GetUUIDMetadata; +import com.pubnub.api.endpoints.objects_api.uuid.RemoveUUIDMetadata; +import com.pubnub.api.endpoints.objects_api.uuid.SetUUIDMetadata; +import com.pubnub.api.endpoints.presence.GetState; +import com.pubnub.api.endpoints.presence.HereNow; +import com.pubnub.api.endpoints.presence.SetState; +import com.pubnub.api.endpoints.presence.WhereNow; +import com.pubnub.api.endpoints.pubsub.Publish; +import com.pubnub.api.endpoints.pubsub.Signal; +import com.pubnub.api.endpoints.push.AddChannelsToPush; +import com.pubnub.api.endpoints.push.ListPushProvisions; +import com.pubnub.api.endpoints.push.RemoveAllPushChannelsForDevice; +import com.pubnub.api.endpoints.push.RemoveChannelsFromPush; +import com.pubnub.api.managers.BasePathManager; +import com.pubnub.api.managers.DelayedReconnectionManager; +import com.pubnub.api.managers.DuplicationManager; +import com.pubnub.api.managers.ListenerManager; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.PublishSequenceManager; +import com.pubnub.api.managers.ReconnectionManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.StateManager; +import com.pubnub.api.managers.SubscriptionManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.managers.token_manager.TokenParser; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import com.pubnub.api.vendor.Crypto; +import com.pubnub.api.vendor.FileEncryptionUtil; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.util.Date; +import java.util.List; +import java.util.UUID; + + +public class PubNub { + + @Getter + private @NotNull PNConfiguration configuration; + + @Getter + private @NotNull MapperManager mapper; + + private String instanceId; + + private SubscriptionManager subscriptionManager; + + private BasePathManager basePathManager; + + private PublishSequenceManager publishSequenceManager; + + private TelemetryManager telemetryManager; + + private RetrofitManager retrofitManager; + + private final TokenParser tokenParser; + + private static final int TIMESTAMP_DIVIDER = 1000; + private static final int MAX_SEQUENCE = 65535; + + private static final String SDK_VERSION = "5.2.1"; + private final ListenerManager listenerManager; + private final StateManager stateManager; + + private final TokenManager tokenManager; + + public PubNub(@NotNull PNConfiguration initialConfig) { + this.configuration = initialConfig; + this.mapper = new MapperManager(); + this.telemetryManager = new TelemetryManager(); + this.basePathManager = new BasePathManager(initialConfig); + this.retrofitManager = new RetrofitManager(this); + this.listenerManager = new ListenerManager(this); + this.stateManager = new StateManager(this.configuration); + this.tokenManager = new TokenManager(); + final ReconnectionManager reconnectionManager = new ReconnectionManager(this); + final DelayedReconnectionManager delayedReconnectionManager = new DelayedReconnectionManager(this); + final DuplicationManager duplicationManager = new DuplicationManager(this.configuration); + this.subscriptionManager = new SubscriptionManager(this, + retrofitManager, + this.telemetryManager, + stateManager, + listenerManager, + reconnectionManager, + delayedReconnectionManager, + duplicationManager, + tokenManager); + this.publishSequenceManager = new PublishSequenceManager(MAX_SEQUENCE); + this.tokenParser = new TokenParser(); + instanceId = UUID.randomUUID().toString(); + } + + @NotNull + public String getBaseUrl() { + return this.basePathManager.getBasePath(); + } + + + public void addListener(@NotNull SubscribeCallback listener) { + listenerManager.addListener(listener); + } + + public void removeListener(@NotNull SubscribeCallback listener) { + listenerManager.removeListener(listener); + } + + @NotNull + public SubscribeBuilder subscribe() { + return new SubscribeBuilder(this.subscriptionManager); + } + + @NotNull + public UnsubscribeBuilder unsubscribe() { + return new UnsubscribeBuilder(this.subscriptionManager); + } + + @NotNull + public PresenceBuilder presence() { + return new PresenceBuilder(this.subscriptionManager); + } + + // start push + + @NotNull + public AddChannelsToPush addPushNotificationsOnChannels() { + return new AddChannelsToPush(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveChannelsFromPush removePushNotificationsFromChannels() { + return new RemoveChannelsFromPush(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveAllPushChannelsForDevice removeAllPushNotificationsFromDeviceWithPushToken() { + return new RemoveAllPushChannelsForDevice(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public ListPushProvisions auditPushChannelProvisions() { + return new ListPushProvisions(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + // end push + + @NotNull + public WhereNow whereNow() { + return new WhereNow(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public HereNow hereNow() { + return new HereNow(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public Time time() { + return new Time(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public History history() { + return new History(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public FetchMessages fetchMessages() { + return new FetchMessages(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public DeleteMessages deleteMessages() { + return new DeleteMessages(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public MessageCounts messageCounts() { + return new MessageCounts(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public Grant grant() { + return new Grant(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GrantToken grantToken() { + return new GrantToken(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetState getPresenceState() { + return new GetState(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public SetState setPresenceState() { + return new SetState(this, subscriptionManager, this.telemetryManager, this.retrofitManager, tokenManager); + } + + @NotNull + public Publish publish() { + return new Publish(this, publishSequenceManager, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public Signal signal() { + return new Signal(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public ListAllChannelGroup listAllChannelGroups() { + return new ListAllChannelGroup(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public AllChannelsChannelGroup listChannelsForChannelGroup() { + return new AllChannelsChannelGroup(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public AddChannelChannelGroup addChannelsToChannelGroup() { + return new AddChannelChannelGroup(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveChannelChannelGroup removeChannelsFromChannelGroup() { + return new RemoveChannelChannelGroup(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public DeleteChannelGroup deleteChannelGroup() { + return new DeleteChannelGroup(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + // Start Objects API + + public SetUUIDMetadata setUUIDMetadata() { + return SetUUIDMetadata.create(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetAllUUIDMetadata getAllUUIDMetadata() { + return GetAllUUIDMetadata.create(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetUUIDMetadata getUUIDMetadata() { + return GetUUIDMetadata.create(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveUUIDMetadata removeUUIDMetadata() { + return new RemoveUUIDMetadata(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + public SetChannelMetadata.Builder setChannelMetadata() { + return SetChannelMetadata.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetAllChannelsMetadata getAllChannelsMetadata() { + return GetAllChannelsMetadata.create(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetChannelMetadata.Builder getChannelMetadata() { + return GetChannelMetadata.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + public RemoveChannelMetadata.Builder removeChannelMetadata() { + return RemoveChannelMetadata.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetMemberships getMemberships() { + return GetMemberships.create(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public SetMemberships.Builder setMemberships() { + return SetMemberships.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveMemberships.Builder removeMemberships() { + return RemoveMemberships.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public ManageMemberships.Builder manageMemberships() { + return ManageMemberships.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetChannelMembers.Builder getChannelMembers() { + return GetChannelMembers.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public SetChannelMembers.Builder setChannelMembers() { + return SetChannelMembers.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveChannelMembers.Builder removeChannelMembers() { + return RemoveChannelMembers.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public ManageChannelMembers.Builder manageChannelMembers() { + return ManageChannelMembers.builder(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + // End Objects API + + // Start Message Actions API + + @NotNull + public AddMessageAction addMessageAction() { + return new AddMessageAction(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public GetMessageActions getMessageActions() { + return new GetMessageActions(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + @NotNull + public RemoveMessageAction removeMessageAction() { + return new RemoveMessageAction(this, this.telemetryManager, this.retrofitManager, this.tokenManager); + } + + // End Message Actions API + + @NotNull + public SendFile.Builder sendFile() { + return SendFile.builder(this, telemetryManager, retrofitManager, tokenManager); + } + + public ListFiles.Builder listFiles() { + return new ListFiles.Builder(this, telemetryManager, retrofitManager, tokenManager); + } + + public GetFileUrl.Builder getFileUrl() { + return GetFileUrl.builder( + this, + telemetryManager, + retrofitManager, + tokenManager); + } + + public DownloadFile.Builder downloadFile() { + return DownloadFile.builder( + this, + telemetryManager, + retrofitManager, + tokenManager); + } + + public DeleteFile.Builder deleteFile() { + return DeleteFile.builder( + this, + telemetryManager, + retrofitManager, + tokenManager); + } + + public PublishFileMessage.Builder publishFileMessage() { + return PublishFileMessage.builder( + this, + telemetryManager, + retrofitManager, + tokenManager); + } + + // public methods + + /** + * Perform Cryptographic decryption of an input string using cipher key provided by PNConfiguration + * + * @param inputString String to be encrypted + * @return String containing the encryption of inputString using cipherKey + */ + @Nullable + public String decrypt(String inputString) throws PubNubException { + if (inputString == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build(); + } + + return decrypt(inputString, this.getConfiguration().getCipherKey()); + } + + /** + * Perform Cryptographic decryption of an input string using the cipher key + * + * @param inputString String to be encrypted + * @param cipherKey cipher key to be used for encryption + * @return String containing the encryption of inputString using cipherKey + * @throws PubNubException throws exception in case of failed encryption + */ + @Nullable + public String decrypt(String inputString, String cipherKey) throws PubNubException { + if (inputString == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build(); + } + boolean dynamicIV = this.getConfiguration().isUseRandomInitializationVector(); + return new Crypto(cipherKey, dynamicIV).decrypt(inputString); + } + + public InputStream decryptInputStream(InputStream inputStream) throws PubNubException { + return decryptInputStream(inputStream, this.getConfiguration().getCipherKey()); + } + + public InputStream decryptInputStream(InputStream inputStream, String cipherKey) throws PubNubException { + return FileEncryptionUtil.decrypt(cipherKey, inputStream); + } + + /** + * Perform Cryptographic encryption of an input string and the cipher key provided by PNConfiguration + * + * @param inputString String to be encrypted + * @return String containing the encryption of inputString using cipherKey + */ + @Nullable + public String encrypt(String inputString) throws PubNubException { + if (inputString == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build(); + } + + return encrypt(inputString, this.getConfiguration().getCipherKey()); + } + + /** + * Perform Cryptographic encryption of an input string and the cipher key. + * + * @param inputString String to be encrypted + * @param cipherKey cipher key to be used for encryption + * @return String containing the encryption of inputString using cipherKey + * @throws PubNubException throws exception in case of failed encryption + */ + @Nullable + public String encrypt(String inputString, String cipherKey) throws PubNubException { + if (inputString == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build(); + } + + boolean dynamicIV = this.getConfiguration().isUseRandomInitializationVector(); + return new Crypto(cipherKey, dynamicIV).encrypt(inputString); + } + + public InputStream encryptInputStream(InputStream inputStream) throws PubNubException { + return encryptInputStream(inputStream, this.getConfiguration().getCipherKey()); + } + + public InputStream encryptInputStream(InputStream inputStream, String cipherKey) throws PubNubException { + return FileEncryptionUtil.encrypt(cipherKey, inputStream); + } + + public int getTimestamp() { + return (int) ((new Date().getTime()) / TIMESTAMP_DIVIDER); + } + + /** + * @return instance uuid. + */ + @NotNull + public String getInstanceId() { + return instanceId; + } + + /** + * @return request uuid. + */ + @NotNull + public String getRequestId() { + return UUID.randomUUID().toString(); + } + + /** + * @return version of the SDK. + */ + @NotNull + public String getVersion() { + return SDK_VERSION; + } + + /** + * Stop the SDK and terminate all listeners. + */ + @Deprecated + public void stop() { + subscriptionManager.stop(); + } + + /** + * Destroy the SDK to cancel all ongoing requests and stop heartbeat timer. + */ + public void destroy() { + try { + subscriptionManager.destroy(false); + retrofitManager.destroy(false); + } catch (Exception error) { + // + } + } + + /** + * Force destroy the SDK to evict the connection pools and close executors. + */ + public void forceDestroy() { + try { + subscriptionManager.destroy(true); + retrofitManager.destroy(true); + telemetryManager.stopCleanUpTimer(); + } catch (Exception error) { + // + } + } + + /** + * Perform a Reconnect to the network + */ + public void reconnect() { + subscriptionManager.reconnect(); + } + + /** + * Perform a disconnect from the listeners + */ + public void disconnect() { + subscriptionManager.disconnect(); + } + + @NotNull + public Publish fire() { + return publish().shouldStore(false).replicate(false); + } + + @NotNull + public List getSubscribedChannels() { + return stateManager.subscriptionStateData(false).getChannels(); + } + + @NotNull + public List getSubscribedChannelGroups() { + return this.stateManager.subscriptionStateData(false).getChannelGroups(); + } + + public void unsubscribeAll() { + subscriptionManager.unsubscribeAll(); + } + + public PNToken parseToken(String token) throws PubNubException { + return tokenParser.unwrapToken(token); + } + + public void setToken(String token) { + tokenManager.setToken(token); + } +} diff --git a/src/main/java/com/pubnub/api/PubNubError.java b/src/main/java/com/pubnub/api/PubNubError.java new file mode 100644 index 000000000..a1a88a50c --- /dev/null +++ b/src/main/java/com/pubnub/api/PubNubError.java @@ -0,0 +1,33 @@ +package com.pubnub.api; + + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +/** + * PubNubError object is passed to errorCallback. It contains details of error, + * like error code, error string, and optional message + * + * @author PubNub + */ +@Getter +@Builder +@ToString +public class PubNubError { + + private int errorCode; + private int errorCodeExtended; + private JsonElement errorObject; + /** + * includes a message from the thrown exception (if any.) + */ + private String message; + /** + * PubNub supplied explanation of the error. + */ + private String errorString; + +} + diff --git a/src/main/java/com/pubnub/api/PubNubException.java b/src/main/java/com/pubnub/api/PubNubException.java new file mode 100644 index 000000000..b663e44fc --- /dev/null +++ b/src/main/java/com/pubnub/api/PubNubException.java @@ -0,0 +1,51 @@ +package com.pubnub.api; + +import com.google.gson.JsonElement; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import retrofit2.Call; + +@Getter +@ToString +public class PubNubException extends Exception { + private String errormsg; + private PubNubError pubnubError; + private JsonElement jso; + private String response; + private int statusCode; + + @Builder + public PubNubException(final String errormsg, + final PubNubError pubnubError, + final JsonElement jso, + final String response, + final int statusCode, + final Call affectedCall, + final Throwable cause) { + super(cause); + this.errormsg = errormsg; + this.pubnubError = pubnubError; + this.jso = jso; + this.response = response; + this.statusCode = statusCode; + this.affectedCall = affectedCall; + } + + @Override + @ToString.Include + public Throwable getCause() { + return super.getCause(); + } + + @Override + public String getMessage() { + return errormsg; + } + + @Getter(AccessLevel.NONE) + @ToString.Exclude + private Call affectedCall; +} + diff --git a/src/main/java/com/pubnub/api/PubNubUtil.java b/src/main/java/com/pubnub/api/PubNubUtil.java new file mode 100644 index 000000000..6bea9355f --- /dev/null +++ b/src/main/java/com/pubnub/api/PubNubUtil.java @@ -0,0 +1,293 @@ +package com.pubnub.api; + +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.vendor.Base64; +import lombok.extern.java.Log; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okio.Buffer; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import static com.pubnub.api.vendor.FileEncryptionUtil.BUFFER_SIZE_BYTES; + +@Log +public class PubNubUtil { + + private static final String CHARSET = "UTF-8"; + public static final String SIGNATURE_QUERY_PARAM_NAME = "signature"; + public static final String TIMESTAMP_QUERY_PARAM_NAME = "timestamp"; + public static final String AUTH_QUERY_PARAM_NAME = "auth"; + + private PubNubUtil() { + } + + public static String joinString(List val, String delim) { + StringBuilder builder = new StringBuilder(); + for (String l : val) { + builder.append(l); + builder.append(delim); + } + + return builder.toString().substring(0, builder.toString().length() - 1); + + } + + public static String joinLong(List val, String delim) { + StringBuilder builder = new StringBuilder(); + for (Long l : val) { + builder.append(Long.toString(l).toLowerCase()); + builder.append(","); + } + + return builder.toString().substring(0, builder.toString().length() - 1); + + } + + /** + * Returns encoded String + * + * @param stringToEncode , input string + * @return , encoded string + */ + public static String pamEncode(String stringToEncode) { + /* !'()*~ */ + + String encoded = urlEncode(stringToEncode); + if (encoded != null) { + encoded = encoded + .replace("*", "%2A") + .replace("!", "%21") + .replace("'", "%27") + .replace("(", "%28") + .replace(")", "%29") + .replace("[", "%5B") + .replace("]", "%5D") + .replace("~", "%7E"); + } + return encoded; + } + + /** + * Returns encoded String + * + * @param stringToEncode , input string + * @return , encoded string + */ + public static String urlEncode(String stringToEncode) { + try { + return URLEncoder.encode(stringToEncode, CHARSET).replace("+", "%20"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + /** + * Returns decoded String + * + * @param stringToEncode , input string + * @return , decoded string + */ + public static String urlDecode(String stringToEncode) { + try { + return URLDecoder.decode(stringToEncode, CHARSET); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public static String preparePamArguments(Map pamArgs) { + Set pamKeys = new TreeSet(pamArgs.keySet()); + String stringifiedArguments = ""; + int i = 0; + + for (String pamKey : pamKeys) { + if (i != 0) { + stringifiedArguments = stringifiedArguments.concat("&"); + } + + stringifiedArguments = + stringifiedArguments.concat(pamKey).concat("=").concat(pamEncode(pamArgs.get(pamKey))); + + i += 1; + } + + return stringifiedArguments; + } + + public static String signSHA256(String key, String data) throws PubNubException, UnsupportedEncodingException { + Mac sha256HMAC; + byte[] hmacData; + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(CHARSET), "HmacSHA256"); + + try { + sha256HMAC = Mac.getInstance("HmacSHA256"); + } catch (NoSuchAlgorithmException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_CRYPTO_ERROR) + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + + try { + sha256HMAC.init(secretKey); + } catch (InvalidKeyException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_CRYPTO_ERROR) + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + + hmacData = sha256HMAC.doFinal(data.getBytes(CHARSET)); + + return new String(Base64.encode(hmacData, 0), CHARSET) + .replace('+', '-') + .replace('/', '_') + .replace("\n", ""); + } + + public static String replaceLast(String string, String toReplace, String replacement) { + int pos = string.lastIndexOf(toReplace); + if (pos > -1) { + return string.substring(0, pos).concat(replacement).concat(string.substring(pos + toReplace.length(), + string.length())); + } else { + return string; + } + } + + public static Request signRequest(Request originalRequest, PNConfiguration pnConfiguration, int timestamp) { + // only sign if we have a secret key in place. + if (!shouldSignRequest(pnConfiguration)) { + return originalRequest; + } + + String signature = generateSignature(pnConfiguration, originalRequest, timestamp); + + HttpUrl rebuiltUrl = originalRequest.url().newBuilder() + .addQueryParameter(TIMESTAMP_QUERY_PARAM_NAME, String.valueOf(timestamp)) + .addQueryParameter(SIGNATURE_QUERY_PARAM_NAME, signature) + .build(); + + return originalRequest.newBuilder().url(rebuiltUrl).build(); + } + + public static boolean shouldSignRequest(PNConfiguration pnConfiguration) { + return pnConfiguration.getSecretKey() != null; + } + + public static String generateSignature(PNConfiguration configuration, + String requestURL, + Map queryParams, + String method, + String requestBody, + int timestamp) { + boolean isV2Signature; + + StringBuilder signatureBuilder = new StringBuilder(); + + queryParams.put(TIMESTAMP_QUERY_PARAM_NAME, String.valueOf(timestamp)); + String encodedQueryString = PubNubUtil.preparePamArguments(queryParams); + + isV2Signature = !(requestURL.startsWith("/publish") && method.equalsIgnoreCase("post")); + + if (!isV2Signature) { + signatureBuilder.append(configuration.getSubscribeKey()).append("\n"); + signatureBuilder.append(configuration.getPublishKey()).append("\n"); + signatureBuilder.append(requestURL).append("\n"); + signatureBuilder.append(encodedQueryString); + } else { + signatureBuilder.append(method.toUpperCase()).append("\n"); + signatureBuilder.append(configuration.getPublishKey()).append("\n"); + signatureBuilder.append(requestURL).append("\n"); + signatureBuilder.append(encodedQueryString).append("\n"); + signatureBuilder.append(requestBody); + } + + String signature = ""; + try { + signature = PubNubUtil.signSHA256(configuration.getSecretKey(), signatureBuilder.toString()); + if (isV2Signature) { + signature = removeTrailingEqualSigns(signature); + signature = "v2.".concat(signature); + } + } catch (PubNubException | UnsupportedEncodingException e) { + log.warning("signature failed on SignatureInterceptor: " + e.toString()); + } + + return signature; + } + + private static String generateSignature(PNConfiguration configuration, Request request, int timestamp) { + Map queryParams = new HashMap<>(); + for (String queryKey : request.url().queryParameterNames()) { + queryParams.put(queryKey, request.url().queryParameter(queryKey)); + } + return generateSignature(configuration, + request.url().encodedPath(), + queryParams, + request.method(), + requestBodyToString(request), + timestamp); + + } + + public static String removeTrailingEqualSigns(String signature) { + String cleanSignature = signature; + + while ((cleanSignature.charAt(cleanSignature.length() - 1) == '=')) { + cleanSignature = cleanSignature.substring(0, cleanSignature.length() - 1); + } + return cleanSignature; + } + + private static String requestBodyToString(final Request request) { + if (request.body() == null) { + return ""; + } + try { + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + return buffer.readUtf8(); + } catch (final IOException e) { + e.printStackTrace(); + } + return ""; + } + + public static byte[] readBytes(final InputStream inputStream) throws IOException { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + int read; + final byte[] buffer = new byte[BUFFER_SIZE_BYTES]; + do { + read = inputStream.read(buffer); + if (read != -1) { + byteArrayOutputStream.write(buffer, 0, read); + } + } while (read != -1); + return byteArrayOutputStream.toByteArray(); + } + } + + public static boolean isNullOrEmpty(final Collection collection) { + return collection == null || collection.isEmpty(); + } + +} diff --git a/src/main/java/com/pubnub/api/builder/PresenceBuilder.java b/src/main/java/com/pubnub/api/builder/PresenceBuilder.java new file mode 100644 index 000000000..79f687fa1 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/PresenceBuilder.java @@ -0,0 +1,40 @@ +package com.pubnub.api.builder; + +import com.pubnub.api.builder.dto.PresenceOperation; +import com.pubnub.api.managers.SubscriptionManager; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +@Setter +@Accessors(chain = true, fluent = true) +public class PresenceBuilder extends PubSubBuilder { + + @Setter(AccessLevel.PUBLIC) + private boolean connected; + + public PresenceBuilder(SubscriptionManager subscriptionManager) { + super(subscriptionManager); + } + + public void execute() { + PresenceOperation presenceOperation = PresenceOperation.builder() + .channels(this.getChannelSubscriptions()) + .channelGroups(this.getChannelGroupSubscriptions()) + .connected(connected) + .build(); + + this.getSubscriptionManager().adaptPresenceBuilder(presenceOperation); + } + + public PresenceBuilder channels(List channels) { + return (PresenceBuilder) super.channels(channels); + } + + public PresenceBuilder channelGroups(List channelGroups) { + return (PresenceBuilder) super.channelGroups(channelGroups); + } + +} diff --git a/src/main/java/com/pubnub/api/builder/PubNubErrorBuilder.java b/src/main/java/com/pubnub/api/builder/PubNubErrorBuilder.java new file mode 100644 index 000000000..614fbbafc --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/PubNubErrorBuilder.java @@ -0,0 +1,705 @@ +package com.pubnub.api.builder; + +import com.pubnub.api.PubNubError; + + +public final class PubNubErrorBuilder { + + // Error Codes + /** + * Timeout Error . + */ + public static final int PNERR_TIMEOUT = 100; + + /** + * + */ + public static final int PNERR_PUBNUB_ERROR = 101; + + /** + * Connect Exception . Network Unreachable. + */ + public static final int PNERR_CONNECT_EXCEPTION = 102; + + /** + * Please check network connectivity. Please contact support with error + * details if issue persists. + */ + public static final int PNERR_HTTP_ERROR = 103; + + /** + * Client Timeout . + */ + public static final int PNERR_CLIENT_TIMEOUT = 104; + + /** + * An ULS singature error occurred . Please contact support with error + * details. + */ + public static final int PNERR_ULSSIGN_ERROR = 105; + + /** + * Please verify if network is reachable + */ + public static final int PNERR_NETWORK_ERROR = 106; + + /** + * PubNub Exception . + */ + public static final int PNERR_PUBNUB_EXCEPTION = 108; + + /** + * Disconnect . + */ + public static final int PNERR_DISCONNECT = 109; + + /** + * Disconnect and Resubscribe Received . + */ + public static final int PNERR_DISCONN_AND_RESUB = 110; + + /** + * Gateway Timeout + */ + public static final int PNERR_GATEWAY_TIMEOUT = 111; + + /** + * PubNub server returned HTTP 403 forbidden status code. Happens when wrong + * authentication key is used . + */ + public static final int PNERR_FORBIDDEN = 112; + /** + * PubNub server returned HTTP 401 unauthorized status code Happens when + * authentication key is missing . + */ + public static final int PNERR_UNAUTHORIZED = 113; + + /** + * Secret key not configured + */ + public static final int PNERR_SECRET_KEY_MISSING = 114; + + // internal error codes + + /** + * Error while encrypting message to be published to PubNub Cloud . Please + * contact support with error details. + */ + public static final int PNERR_ENCRYPTION_ERROR = 115; + + /** + * Decryption Error . Please contact support with error details. + */ + public static final int PNERR_DECRYPTION_ERROR = 116; + + /** + * Invalid Json . Please contact support with error details. + */ + public static final int PNERR_INVALID_JSON = 117; + + /** + * Unable to open input stream . Please contact support with error details. + */ + public static final int PNERR_GETINPUTSTREAM = 118; + + /** + * Malformed URL . Please contact support with error details . + */ + public static final int PNERR_MALFORMED_URL = 119; + + /** + * Error in opening URL . Please contact support with error details. + */ + public static final int PNERR_URL_OPEN = 120; + + /** + * JSON Error while processing API response. Please contact support with + * error details. + */ + public static final int PNERR_JSON_ERROR = 121; + + /** + * Protocol Exception . Please contact support with error details. + */ + public static final int PNERR_PROTOCOL_EXCEPTION = 122; + + /** + * Unable to read input stream . Please contact support with error details. + */ + public static final int PNERR_READINPUT = 123; + + /** + * Bad gateway . Please contact support with error details. + */ + public static final int PNERR_BAD_GATEWAY = 124; + + /** + * PubNub server returned HTTP 502 internal server error status code. Please + * contact support with error details. + */ + public static final int PNERR_INTERNAL_ERROR = 125; + + /** + * Parsing Error . + */ + public static final int PNERR_PARSING_ERROR = 126; + + /** + * Bad Request . Please contact support with error details. + */ + public static final int PNERR_BAD_REQUEST = 127; + + public static final int PNERR_HTTP_RC_ERROR = 128; + /** + * PubNub server or intermediate server returned HTTP 404 unauthorized + * status code + */ + public static final int PNERR_NOT_FOUND = 129; + + /** + * Subscribe Timeout . + */ + public static final int PNERR_HTTP_SOCKET_TIMEOUT = 130; + + /** + * Invalid arguments provided to API + */ + public static final int PNERR_INVALID_ARGUMENTS = 131; + + /** + * Channel missing + */ + public static final int PNERR_CHANNEL_MISSING = 132; + + /** + * PubNub connection not set on sender + */ + public static final int PNERR_CONNECTION_NOT_SET = 133; + + /** + * Error while parsing group name + */ + public static final int PNERR_CHANNEL_GROUP_PARSING_ERROR = 134; + + /** + * Crypto Error + */ + public static final int PNERR_CRYPTO_ERROR = 135; + + /** + * Group missing + */ + public static final int PNERR_GROUP_MISSING = 136; + + /** + * Auth Keys missing + */ + public static final int PNERR_AUTH_KEYS_MISSING = 137; + + /** + * Subscribe Key missing + */ + public static final int PNERR_SUBSCRIBE_KEY_MISSING = 138; + + /** + * Publish Key missing + */ + public static final int PNERR_PUBLISH_KEY_MISSING = 139; + + /** + * State missing + */ + public static final int PNERR_STATE_MISSING = 140; + + /** + * Channel and Group missing + */ + public static final int PNERR_CHANNEL_AND_GROUP_MISSING = 141; + + /** + * Message missing + */ + public static final int PNERR_MESSAGE_MISSING = 142; + + /** + * Push TYpe missing + */ + public static final int PNERR_PUSH_TYPE_MISSING = 143; + + /** + * Device ID missing + */ + public static final int PNERR_DEVICE_ID_MISSING = 144; + + /** + * Timetoken missing + */ + public static final int PNERR_TIMETOKEN_MISSING = 145; + + /** + * Timetoken missing + */ + public static final int PNERR_CHANNELS_TIMETOKEN_MISMATCH = 146; + + /** + * UUID missing + */ + public static final int PNERR_UUID_MISSING = 147; + + /** + * User ID missing + */ + public static final int PNERR_USER_ID_MISSING = 148; + + /** + * User name missing + */ + public static final int PNERR_USER_NAME_MISSING = 149; + + /** + * Space missing + */ + public static final int PNERR_SPACE_MISSING = 150; + + /** + * Space ID missing + */ + public static final int PNERR_SPACE_ID_MISSING = 151; + + /** + * Space name missing + */ + public static final int PNERR_SPACE_NAME_MISSING = 152; + + /** + * Resources missing + */ + public static final int PNERR_RESOURCES_MISSING = 153; + + /** + * TTL missing + */ + public static final int PNERR_TTL_MISSING = 154; + + /** + * Invalid meta parameter + */ + public static final int PNERR_INVALID_META = 155; + + /** + * Permission missing + */ + public static final int PNERR_PERMISSION_MISSING = 156; + + /** + * Invalid access token + */ + public static final int PNERR_INVALID_ACCESS_TOKEN = 157; + + /** + * Message action missing + */ + public static final int PNERR_MESSAGE_ACTION_MISSING = 158; + + /** + * Message action type missing + */ + public static final int PNERR_MESSAGE_ACTION_TYPE_MISSING = 159; + + /** + * Message action value missing + */ + public static final int PNERR_MESSAGE_ACTION_VALUE_MISSING = 160; + + /** + * Message timetoken missing + */ + public static final int PNERR_MESSAGE_TIMETOKEN_MISSING = 161; + + /** + * Message action timetoken missing + */ + public static final int PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING = 162; + + /** + * Retrieving message actions for multiple channels + */ + public static final int PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS = 163; + + /** + * Push topic missing + */ + public static final int PNERR_PUSH_TOPIC_MISSING = 164; + + /** + * No more pages to load after last one + */ + public static final int PNERR_PAGINATION_NEXT_OUT_OF_BOUNDS = 165; + + /** + * No pages to load before first one + */ + public static final int PNERR_PAGINATION_PREV_OUT_OF_BOUNDS = 166; + + /** + * Payload too large + */ + public static final int PNERR_PAYLOAD_TOO_LARGE = 167; + + // Error Objects + public static final PubNubError PNERROBJ_TIMEOUT = PubNubError.builder() + .errorCode(PNERR_TIMEOUT) + .message("Timeout Occurred") + .build(); + + public static final PubNubError PNERROBJ_INTERNAL_ERROR = PubNubError.builder() + .errorCode(PNERR_INTERNAL_ERROR) + .message("Internal Error") + .build(); + + public static final PubNubError PNERROBJ_ENCRYPTION_ERROR = PubNubError.builder() + .errorCode(PNERR_ENCRYPTION_ERROR) + .message("Error while encrypting message to be published to PubNub Cloud. Please contact support with " + + "error details.") + .build(); + + public static final PubNubError PNERROBJ_DECRYPTION_ERROR = PubNubError.builder() + .errorCode(PNERR_DECRYPTION_ERROR) + .message("Decryption Error. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_INVALID_JSON = PubNubError.builder() + .errorCode(PNERR_INVALID_JSON) + .message("Invalid Json. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_JSON_ERROR = PubNubError.builder() + .errorCode(PNERR_JSON_ERROR) + .message("JSON Error while processing API response. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_MALFORMED_URL = PubNubError.builder() + .errorCode(PNERR_MALFORMED_URL) + .message("Malformed URL. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_PUBNUB_ERROR = PubNubError.builder() + .errorCode(PNERR_PUBNUB_ERROR) + .message("PubNub Error") + .build(); + + public static final PubNubError PNERROBJ_URL_OPEN = PubNubError.builder() + .errorCode(PNERR_URL_OPEN) + .message("Error opening url. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_PROTOCOL_EXCEPTION = PubNubError.builder() + .errorCode(PNERR_PROTOCOL_EXCEPTION) + .message("Protocol Exception. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_CONNECT_EXCEPTION = PubNubError.builder() + .errorCode(PNERR_CONNECT_EXCEPTION) + .message("Connect Exception. Please verify if network is reachable.") + .build(); + + public static final PubNubError PNERROBJ_HTTP_RC_ERROR = PubNubError.builder() + .errorCode(PNERR_HTTP_RC_ERROR) + .message("Unable to get PnResponse Code. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_GETINPUTSTREAM = PubNubError.builder() + .errorCode(PNERR_GETINPUTSTREAM) + .message("Unable to get Input Stream Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_READINPUT = PubNubError.builder() + .errorCode(PNERR_READINPUT) + .message("Unable to read Input Stream. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_BAD_REQUEST = PubNubError.builder() + .errorCode(PNERR_BAD_REQUEST) + .message("Bad request. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_HTTP_ERROR = PubNubError.builder() + .errorCode(PNERR_HTTP_ERROR) + .message("HTTP Error. Please check network connectivity. Please contact support with error details if " + + "issue persists.") + .build(); + + public static final PubNubError PNERROBJ_BAD_GATEWAY = PubNubError.builder() + .errorCode(PNERR_BAD_GATEWAY) + .message("Bad Gateway. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_CLIENT_TIMEOUT = PubNubError.builder() + .errorCode(PNERR_CLIENT_TIMEOUT) + .message("Client Timeout") + .build(); + + public static final PubNubError PNERROBJ_GATEWAY_TIMEOUT = PubNubError.builder() + .errorCode(PNERR_GATEWAY_TIMEOUT) + .message("Gateway Timeout") + .build(); + + public static final PubNubError PNERROBJ_5023_INTERNAL_ERROR = PubNubError.builder() + .errorCode(PNERR_INTERNAL_ERROR) + .message("Internal Server Error. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_PARSING_ERROR = PubNubError.builder() + .errorCode(PNERR_PARSING_ERROR) + .message("Parsing Error") + .build(); + + public static final PubNubError PNERROBJ_PUBNUB_EXCEPTION = PubNubError.builder() + .errorCode(PNERR_PUBNUB_EXCEPTION) + .message("PubNub Exception") + .build(); + + public static final PubNubError PNERROBJ_DISCONNECT = PubNubError.builder() + .errorCode(PNERR_DISCONNECT) + .message("Disconnect") + .build(); + + public static final PubNubError PNERROBJ_DISCONN_AND_RESUB = PubNubError.builder() + .errorCode(PNERR_DISCONN_AND_RESUB) + .message("Disconnect and Resubscribe") + .build(); + + public static final PubNubError PNERROBJ_FORBIDDEN = PubNubError.builder() + .errorCode(PNERR_FORBIDDEN) + .message("Authentication Failure. Incorrect Authentication Key") + .build(); + + public static final PubNubError PNERROBJ_UNAUTHORIZED = PubNubError.builder() + .errorCode(PNERR_UNAUTHORIZED) + .message("Authentication Failure. Authentication Key is missing") + .build(); + + public static final PubNubError PNERROBJ_SECRET_KEY_MISSING = PubNubError.builder() + .errorCode(PNERR_SECRET_KEY_MISSING) + .message("ULS configuration failed. Secret Key not configured.") + .build(); + + public static final PubNubError PNERROBJ_SUBSCRIBE_KEY_MISSING = PubNubError.builder() + .errorCode(PNERR_SUBSCRIBE_KEY_MISSING) + .message("ULS configuration failed. Subscribe Key not configured.") + .build(); + + public static final PubNubError PNERROBJ_PUBLISH_KEY_MISSING = PubNubError.builder() + .errorCode(PNERR_PUBLISH_KEY_MISSING) + .message("ULS configuration failed. Publish Key not configured.") + .build(); + + public static final PubNubError PNERROBJ_ULSSIGN_ERROR = PubNubError.builder() + .errorCode(PNERR_ULSSIGN_ERROR) + .message("Invalid Signature. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_5075_NETWORK_ERROR = PubNubError.builder() + .errorCode(PNERR_NETWORK_ERROR) + .message("Network Error. Please verify if network is reachable.") + .build(); + + public static final PubNubError PNERROBJ_NOT_FOUND_ERROR = PubNubError.builder() + .errorCode(PNERR_NOT_FOUND) + .message("Page Not Found Please verify if network is reachable. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_SOCKET_TIMEOUT = PubNubError.builder() + .errorCode(PNERR_HTTP_SOCKET_TIMEOUT) + .message("Socket Timeout.") + .build(); + + public static final PubNubError PNERROBJ_INVALID_ARGUMENTS = PubNubError.builder() + .errorCode(PNERR_INVALID_ARGUMENTS) + .message("INVALID ARGUMENTS.") + .build(); + + public static final PubNubError PNERROBJ_CHANNEL_MISSING = PubNubError.builder() + .errorCode(PNERR_CHANNEL_MISSING) + .message("Channel Missing.") + .build(); + + public static final PubNubError PNERROBJ_STATE_MISSING = PubNubError.builder() + .errorCode(PNERR_STATE_MISSING) + .message("State Missing.") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_MISSING) + .message("Message Missing.") + .build(); + + public static final PubNubError PNERROBJ_PUSH_TYPE_MISSING = PubNubError.builder() + .errorCode(PNERR_PUSH_TYPE_MISSING) + .message("Push Type Missing.") + .build(); + + public static final PubNubError PNERROBJ_DEVICE_ID_MISSING = PubNubError.builder() + .errorCode(PNERR_DEVICE_ID_MISSING) + .message("Device Id Missing.") + .build(); + + public static final PubNubError PNERROBJ_CONNECTION_NOT_SET = PubNubError.builder() + .errorCode(PNERR_CONNECTION_NOT_SET) + .message("PubNub Connection not set") + .build(); + + public static final PubNubError PNERROBJ_GROUP_MISSING = PubNubError.builder() + .errorCode(PNERR_GROUP_MISSING) + .message("Group Missing.") + .build(); + + public static final PubNubError PNERROBJ_CHANNEL_AND_GROUP_MISSING = PubNubError.builder() + .errorCode(PNERR_CHANNEL_AND_GROUP_MISSING) + .message("Channel and Group Missing.") + .build(); + + public static final PubNubError PNERROBJ_AUTH_KEYS_MISSING = PubNubError.builder() + .errorCode(PNERR_AUTH_KEYS_MISSING) + .message("Auth Keys Missing.") + .build(); + + public static final PubNubError PNERROBJ_CHANNEL_GROUP_PARSING_ERROR = PubNubError.builder() + .errorCode(PNERR_CHANNEL_GROUP_PARSING_ERROR) + .message("Channel group name is invalid") + .build(); + + public static final PubNubError PNERROBJ_CRYPTO_ERROR = PubNubError.builder() + .errorCode(PNERR_CRYPTO_ERROR) + .message("Error while encrypting/decrypting message. Please contact support with error details.") + .build(); + + public static final PubNubError PNERROBJ_TIMETOKEN_MISSING = PubNubError.builder() + .errorCode(PNERR_TIMETOKEN_MISSING) + .message("Timetoken Missing.") + .build(); + + public static final PubNubError PNERROBJ_CHANNELS_TIMETOKEN_MISMATCH = PubNubError.builder() + .errorCode(PNERR_CHANNELS_TIMETOKEN_MISMATCH) + .message("Channels and timetokens are not equal in size.") + .build(); + + public static final PubNubError PNERROBJ_UUID_MISSING = PubNubError.builder() + .errorCode(PNERR_UUID_MISSING) + .message("UUID is missing") + .build(); + + public static final PubNubError PNERROBJ_USER_ID_MISSING = PubNubError.builder() + .errorCode(PNERR_USER_ID_MISSING) + .message("User ID is missing") + .build(); + + public static final PubNubError PNERROBJ_USER_NAME_MISSING = PubNubError.builder() + .errorCode(PNERR_USER_NAME_MISSING) + .message("User name is missing") + .build(); + + public static final PubNubError PNERROBJ_SPACE_MISSING = PubNubError.builder() + .errorCode(PNERR_SPACE_MISSING) + .message("Space is missing") + .build(); + + public static final PubNubError PNERROBJ_SPACE_ID_MISSING = PubNubError.builder() + .errorCode(PNERR_SPACE_ID_MISSING) + .message("Space ID is missing") + .build(); + + public static final PubNubError PNERROBJ_SPACE_NAME_MISSING = PubNubError.builder() + .errorCode(PNERR_SPACE_NAME_MISSING) + .message("Space name is missing") + .build(); + + public static final PubNubError PNERROBJ_RESOURCES_MISSING = PubNubError.builder() + .errorCode(PNERR_RESOURCES_MISSING) + .message("Resources missing") + .build(); + + public static final PubNubError PNERROBJ_TTL_MISSING = PubNubError.builder() + .errorCode(PNERR_TTL_MISSING) + .message("TTL missing") + .build(); + + public static final PubNubError PNERROBJ_INVALID_META = PubNubError.builder() + .errorCode(PNERR_INVALID_META) + .message("Invalid meta parameter") + .build(); + + public static final PubNubError PNERROBJ_PERMISSION_MISSING = PubNubError.builder() + .errorCode(PNERR_PERMISSION_MISSING) + .message("Permission missing") + .build(); + + public static final PubNubError PNERROBJ_INVALID_ACCESS_TOKEN = PubNubError.builder() + .errorCode(PNERR_INVALID_ACCESS_TOKEN) + .message("Invalid access token") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_ACTION_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_ACTION_MISSING) + .message("Message action is missing") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_ACTION_TYPE_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_ACTION_TYPE_MISSING) + .message("Message action type is missing") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_ACTION_VALUE_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_ACTION_VALUE_MISSING) + .message("Message action value is missing") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_TIMETOKEN_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_TIMETOKEN_MISSING) + .message("Message timetoken is missing") + .build(); + + public static final PubNubError PNERROBJ_MESSAGE_ACTION_TIMETOKEN_MISSING = PubNubError.builder() + .errorCode(PNERR_MESSAGE_ACTION_TIMETOKEN_MISSING) + .message("Message action timetoken is missing") + .build(); + + public static final PubNubError PNERROBJ_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS = PubNubError.builder() + .errorCode(PNERR_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS) + .message("History can return message action data for a single channel only. " + .concat("Either pass a single channel or disable the includeMessageActions flag.")) + .build(); + + public static final PubNubError PNERROBJ_PUSH_TOPIC_MISSING = PubNubError.builder() + .errorCode(PNERR_PUSH_TOPIC_MISSING) + .message("Push notification topic is missing. Required only if push type is APNS2.") + .build(); + + public static final PubNubError PNERROBJ_PAGINATION_NEXT_OUT_OF_BOUNDS = PubNubError.builder() + .errorCode(PNERR_PAGINATION_NEXT_OUT_OF_BOUNDS) + .message("No more pages to load after last one.") + .build(); + + public static final PubNubError PNERROBJ_PAGINATION_PREV_OUT_OF_BOUNDS = PubNubError.builder() + .errorCode(PNERR_PAGINATION_PREV_OUT_OF_BOUNDS) + .message("No pages to load before first one.") + .build(); + + public static final PubNubError PNERROBJ_PAYLOAD_TOO_LARGE = PubNubError.builder() + .errorCode(PNERR_PAYLOAD_TOO_LARGE) + .message("Payload too large.") + .build(); + + private PubNubErrorBuilder() { + + } + + public static PubNubError createCryptoError(int code, String message) { + return PubNubError.builder() + .errorCode(PNERR_CRYPTO_ERROR) + .errorCodeExtended(code) + .message("Error while encrypting/decrypting message. Please contact support with error details. - ".concat(message)) + .build(); + } + +} diff --git a/src/main/java/com/pubnub/api/builder/PubSubBuilder.java b/src/main/java/com/pubnub/api/builder/PubSubBuilder.java new file mode 100644 index 000000000..65e8ae8df --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/PubSubBuilder.java @@ -0,0 +1,45 @@ +package com.pubnub.api.builder; + + +import com.pubnub.api.managers.SubscriptionManager; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +public abstract class PubSubBuilder { + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private List channelSubscriptions; + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private List channelGroupSubscriptions; + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private SubscriptionManager subscriptionManager; + + public PubSubBuilder(SubscriptionManager subscriptionManagerInstance) { + this.subscriptionManager = subscriptionManagerInstance; + this.channelSubscriptions = new ArrayList<>(); + this.channelGroupSubscriptions = new ArrayList<>(); + } + + + public PubSubBuilder channels(List channel) { + channelSubscriptions.addAll(channel); + return this; + } + + public PubSubBuilder channelGroups(List channelGroup) { + channelGroupSubscriptions.addAll(channelGroup); + return this; + } + + public abstract void execute(); + +} diff --git a/src/main/java/com/pubnub/api/builder/SubscribeBuilder.java b/src/main/java/com/pubnub/api/builder/SubscribeBuilder.java new file mode 100644 index 000000000..3b58d743e --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/SubscribeBuilder.java @@ -0,0 +1,60 @@ +package com.pubnub.api.builder; + +import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.managers.SubscriptionManager; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +@Setter +@Accessors(chain = true, fluent = true) +public class SubscribeBuilder extends PubSubBuilder { + + /** + * Allow users to specify if they would also like to include the presence channels for those subscriptions. + */ + @Setter(AccessLevel.NONE) + private boolean presenceEnabled; + + /** + * Allow users to subscribe with a custom timetoken. + */ + @Setter(AccessLevel.NONE) + private Long timetoken; + + public SubscribeBuilder(SubscriptionManager subscriptionManager) { + super(subscriptionManager); + } + + public SubscribeBuilder withPresence() { + this.presenceEnabled = true; + return this; + } + + public SubscribeBuilder withTimetoken(Long timetokenInstance) { + this.timetoken = timetokenInstance; + return this; + } + + public void execute() { + SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(this.getChannelSubscriptions()) + .channelGroups(this.getChannelGroupSubscriptions()) + .timetoken(timetoken) + .presenceEnabled(presenceEnabled) + .build(); + + this.getSubscriptionManager().adaptSubscribeBuilder(subscribeOperation); + } + + public SubscribeBuilder channels(List channels) { + return (SubscribeBuilder) super.channels(channels); + } + + public SubscribeBuilder channelGroups(List channelGroups) { + return (SubscribeBuilder) super.channelGroups(channelGroups); + } + +} diff --git a/src/main/java/com/pubnub/api/builder/UnsubscribeBuilder.java b/src/main/java/com/pubnub/api/builder/UnsubscribeBuilder.java new file mode 100644 index 000000000..ffe73644f --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/UnsubscribeBuilder.java @@ -0,0 +1,26 @@ +package com.pubnub.api.builder; + +import com.pubnub.api.builder.dto.UnsubscribeOperation; +import com.pubnub.api.managers.SubscriptionManager; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UnsubscribeBuilder extends PubSubBuilder { + + public UnsubscribeBuilder(SubscriptionManager subscriptionManager) { + super(subscriptionManager); + } + + public void execute() { + + UnsubscribeOperation unsubscribeOperation = UnsubscribeOperation.builder() + .channels(this.getChannelSubscriptions()) + .channelGroups(this.getChannelGroupSubscriptions()) + .build(); + + this.getSubscriptionManager().adaptUnsubscribeBuilder(unsubscribeOperation); + } + +} diff --git a/src/main/java/com/pubnub/api/builder/dto/ChangeTemporaryUnavailableOperation.java b/src/main/java/com/pubnub/api/builder/dto/ChangeTemporaryUnavailableOperation.java new file mode 100644 index 000000000..67a7045b4 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/ChangeTemporaryUnavailableOperation.java @@ -0,0 +1,20 @@ +package com.pubnub.api.builder.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.Singular; + +import java.util.List; + +@Builder +@Data +public class ChangeTemporaryUnavailableOperation implements PubSubOperation { + @Singular + private final List unavailableChannels; + @Singular + private final List unavailableChannelGroups; + @Singular + private final List availableChannels; + @Singular + private final List availableChannelGroups; +} diff --git a/src/main/java/com/pubnub/api/builder/dto/PresenceOperation.java b/src/main/java/com/pubnub/api/builder/dto/PresenceOperation.java new file mode 100644 index 000000000..e8be0df16 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/PresenceOperation.java @@ -0,0 +1,15 @@ +package com.pubnub.api.builder.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Builder +@Data +public class PresenceOperation implements PubSubOperation { + @Builder.Default private final List channels = Collections.emptyList(); + @Builder.Default private final List channelGroups = Collections.emptyList(); + private final boolean connected; +} diff --git a/src/main/java/com/pubnub/api/builder/dto/PubSubOperation.java b/src/main/java/com/pubnub/api/builder/dto/PubSubOperation.java new file mode 100644 index 000000000..4d88946d3 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/PubSubOperation.java @@ -0,0 +1,31 @@ +package com.pubnub.api.builder.dto; + +import lombok.Data; + +@SuppressWarnings("checkstyle:interfaceistype") +public interface PubSubOperation { + PubSubOperation NO_OP = new NoOpOperation(); + PubSubOperation DISCONNECT = new DisconnectOperation(); + PubSubOperation STATUS_ANNOUNCED = new ConnectedStatusAnnouncedOperation(); + + @Data + class NoOpOperation implements PubSubOperation { + private NoOpOperation() { + + } + } + + @Data + class DisconnectOperation implements PubSubOperation { + private DisconnectOperation() { + + } + } + + @Data + class ConnectedStatusAnnouncedOperation implements PubSubOperation { + private ConnectedStatusAnnouncedOperation() { + + } + } +} diff --git a/src/main/java/com/pubnub/api/builder/dto/StateOperation.java b/src/main/java/com/pubnub/api/builder/dto/StateOperation.java new file mode 100644 index 000000000..ba2b29782 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/StateOperation.java @@ -0,0 +1,15 @@ +package com.pubnub.api.builder.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Builder +@Data +public class StateOperation implements PubSubOperation { + @Builder.Default private final List channels = Collections.emptyList(); + @Builder.Default private final List channelGroups = Collections.emptyList(); + private final Object state; +} diff --git a/src/main/java/com/pubnub/api/builder/dto/SubscribeOperation.java b/src/main/java/com/pubnub/api/builder/dto/SubscribeOperation.java new file mode 100644 index 000000000..b0754fe3c --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/SubscribeOperation.java @@ -0,0 +1,16 @@ +package com.pubnub.api.builder.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.List; + +@Builder +@Data +public class SubscribeOperation implements PubSubOperation { + @Builder.Default private final List channels = Collections.emptyList(); + @Builder.Default private final List channelGroups = Collections.emptyList(); + private final boolean presenceEnabled; + private final Long timetoken; +} diff --git a/src/main/java/com/pubnub/api/builder/dto/TimetokenAndRegionOperation.java b/src/main/java/com/pubnub/api/builder/dto/TimetokenAndRegionOperation.java new file mode 100644 index 000000000..0693431c3 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/TimetokenAndRegionOperation.java @@ -0,0 +1,9 @@ +package com.pubnub.api.builder.dto; + +import lombok.Data; + +@Data +public class TimetokenAndRegionOperation implements PubSubOperation { + private final long timetoken; + private final String region; +} diff --git a/src/main/java/com/pubnub/api/builder/dto/UnsubscribeOperation.java b/src/main/java/com/pubnub/api/builder/dto/UnsubscribeOperation.java new file mode 100644 index 000000000..f29b23261 --- /dev/null +++ b/src/main/java/com/pubnub/api/builder/dto/UnsubscribeOperation.java @@ -0,0 +1,13 @@ +package com.pubnub.api.builder.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Builder +@Data +public class UnsubscribeOperation implements PubSubOperation { + private final List channels; + private final List channelGroups; +} diff --git a/src/main/java/com/pubnub/api/callbacks/PNCallback.java b/src/main/java/com/pubnub/api/callbacks/PNCallback.java new file mode 100644 index 000000000..e419ecf88 --- /dev/null +++ b/src/main/java/com/pubnub/api/callbacks/PNCallback.java @@ -0,0 +1,10 @@ +package com.pubnub.api.callbacks; + +import com.pubnub.api.models.consumer.PNStatus; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface PNCallback<@Nullable X> { + void onResponse(@Nullable X result, @NotNull PNStatus status); +} diff --git a/src/main/java/com/pubnub/api/callbacks/ReconnectionCallback.java b/src/main/java/com/pubnub/api/callbacks/ReconnectionCallback.java new file mode 100644 index 000000000..1710cdcc0 --- /dev/null +++ b/src/main/java/com/pubnub/api/callbacks/ReconnectionCallback.java @@ -0,0 +1,10 @@ +package com.pubnub.api.callbacks; + + +public abstract class ReconnectionCallback { + + public abstract void onReconnection(); + + public abstract void onMaxReconnectionExhaustion(); + +} diff --git a/src/main/java/com/pubnub/api/callbacks/SubscribeCallback.java b/src/main/java/com/pubnub/api/callbacks/SubscribeCallback.java new file mode 100644 index 000000000..0e1d2855e --- /dev/null +++ b/src/main/java/com/pubnub/api/callbacks/SubscribeCallback.java @@ -0,0 +1,81 @@ +package com.pubnub.api.callbacks; + +import com.pubnub.api.PubNub; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.jetbrains.annotations.NotNull; + +public abstract class SubscribeCallback { + public abstract void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus); + + public abstract void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult); + + public abstract void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult); + + public abstract void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult); + + public abstract void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult); + + public abstract void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult); + + public abstract void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult); + + public abstract void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult); + + public abstract void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult); + + public static class BaseSubscribeCallback extends SubscribeCallback { + + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus pnStatus) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + } +} diff --git a/src/main/java/com/pubnub/api/callbacks/TimeCallback.java b/src/main/java/com/pubnub/api/callbacks/TimeCallback.java new file mode 100644 index 000000000..06898cc3c --- /dev/null +++ b/src/main/java/com/pubnub/api/callbacks/TimeCallback.java @@ -0,0 +1,6 @@ +package com.pubnub.api.callbacks; + +import com.pubnub.api.models.consumer.PNTimeResult; + +public abstract class TimeCallback implements PNCallback { +} diff --git a/src/main/java/com/pubnub/api/callbacks/WhereNowCallback.java b/src/main/java/com/pubnub/api/callbacks/WhereNowCallback.java new file mode 100644 index 000000000..123956b47 --- /dev/null +++ b/src/main/java/com/pubnub/api/callbacks/WhereNowCallback.java @@ -0,0 +1,7 @@ +package com.pubnub.api.callbacks; + +import com.pubnub.api.models.consumer.presence.PNWhereNowResult; + + +public abstract class WhereNowCallback implements PNCallback { +} diff --git a/src/main/java/com/pubnub/api/endpoints/BuilderSteps.java b/src/main/java/com/pubnub/api/endpoints/BuilderSteps.java new file mode 100644 index 000000000..e3110c176 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/BuilderSteps.java @@ -0,0 +1,7 @@ +package com.pubnub.api.endpoints; + +public interface BuilderSteps { + interface ChannelStep { + T channel(String channel); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/DeleteMessages.java b/src/main/java/com/pubnub/api/endpoints/DeleteMessages.java new file mode 100644 index 000000000..fa0ea40d8 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/DeleteMessages.java @@ -0,0 +1,102 @@ +package com.pubnub.api.endpoints; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.history.PNDeleteMessagesResult; +import com.pubnub.api.models.server.DeleteMessagesEnvelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) + +public class DeleteMessages extends Endpoint { + + private static final int SERVER_RESPONSE_SUCCESS = 200; + + @Setter + private List channels; + @Setter + private Long start; + @Setter + private Long end; + + public DeleteMessages(PubNub pubnubInstance, + TelemetryManager telemetryManager, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetryManager, retrofitInstance, tokenManager); + channels = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (channels == null || channels.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) throws PubNubException { + + if (start != null) { + params.put("start", Long.toString(start).toLowerCase()); + } + if (end != null) { + params.put("end", Long.toString(end).toLowerCase()); + } + + return this.getRetrofit().getHistoryService().deleteMessages(this.getPubnub().getConfiguration().getSubscribeKey(), PubNubUtil.joinString(channels, ","), params); + } + + @Override + protected PNDeleteMessagesResult createResponse(Response input) throws PubNubException { + if (input.body() == null || input.body().getStatus() == null || input.body().getStatus() != SERVER_RESPONSE_SUCCESS) { + String errorMsg = null; + + if (input.body() != null && input.body().getErrorMessage() != null) { + errorMsg = input.body().getErrorMessage(); + } else { + errorMsg = "n/a"; + } + + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR) + .errormsg(errorMsg) + .build(); + } + + return PNDeleteMessagesResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNDeleteMessagesOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/Endpoint.java b/src/main/java/com/pubnub/api/endpoints/Endpoint.java new file mode 100644 index 000000000..bca8205dd --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/Endpoint.java @@ -0,0 +1,416 @@ +package com.pubnub.api.endpoints; + + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.remoteaction.RemoteAction; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; +import retrofit2.Response; + +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Log +public abstract class Endpoint implements RemoteAction { + + @Getter(AccessLevel.PROTECTED) + private PubNub pubnub; + @Getter(AccessLevel.PROTECTED) + private RetrofitManager retrofit; + + @Getter(AccessLevel.NONE) + private TelemetryManager telemetryManager; + + @Getter(AccessLevel.NONE) + private PNCallback cachedCallback; + + @Getter(AccessLevel.NONE) + private Call call; + + @Setter(AccessLevel.PUBLIC) + @Accessors(chain = true, fluent = true) + private Map queryParam; + + /** + * If the endpoint failed to execute and we do not want to alert the user, flip this to true + * This operation is handy if we internally cancelled the endpoint. + */ + @Getter(AccessLevel.NONE) + private boolean silenceFailures; + + private MapperManager mapper; + + private final TokenManager tokenManager; + + public Endpoint(PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + this.pubnub = pubnubInstance; + this.retrofit = retrofitInstance; + this.tokenManager = tokenManager; + + this.mapper = this.pubnub.getMapper(); + this.telemetryManager = telemetry; + } + + @Override + @Nullable + public Output sync() throws PubNubException { + this.validateParams(); + + call = doWork(createBaseParams()); + Response serverResponse; + Output response; + + try { + serverResponse = call.execute(); + } catch (IOException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR) + .errormsg(e.toString()) + .affectedCall(call) + .cause(e) + .build(); + } + + if (isError(serverResponse)) { + String responseBodyText; + JsonElement responseBody; + + try { + responseBodyText = serverResponse.errorBody().string(); + } catch (IOException e) { + responseBodyText = "N/A"; + } + + try { + responseBody = mapper.fromJson(responseBodyText, JsonElement.class); + } catch (PubNubException e) { + responseBody = null; + } + + throw createPubNubException(serverResponse, responseBodyText, responseBody); + } + + storeRequestLatency(serverResponse, getOperationType()); + response = createResponse(serverResponse); + + return response; + } + + @Override + public void async(@NotNull final PNCallback callback) { + cachedCallback = callback; + + try { + this.validateParams(); + call = doWork(createBaseParams()); + } catch (PubNubException pubnubException) { + callback.onResponse(null, + createStatusResponse(PNStatusCategory.PNBadRequestCategory, null, pubnubException, + null, null)); + return; + } + + call.enqueue(new retrofit2.Callback() { + + @Override + public void onResponse(Call performedCall, Response response) { + Output callbackResponse; + + if (isError(response)) { + + String responseBodyText; + JsonElement responseBody; + JsonElement responseBodyPayload = null; + ArrayList affectedChannels = new ArrayList<>(); + ArrayList affectedChannelGroups = new ArrayList<>(); + + try { + responseBodyText = response.errorBody().string(); + } catch (IOException e) { + responseBodyText = "N/A"; + } + + try { + responseBody = mapper.fromJson(responseBodyText, JsonElement.class); + } catch (PubNubException e) { + responseBody = null; + } + + if (responseBody != null && mapper.isJsonObject(responseBody) && mapper.hasField(responseBody, + "payload")) { + responseBodyPayload = mapper.getField(responseBody, "payload"); + } + + PNStatusCategory pnStatusCategory = PNStatusCategory.PNUnknownCategory; + final PubNubException ex = createPubNubException(response, responseBodyText, responseBody); + + if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) { + pnStatusCategory = PNStatusCategory.PNAccessDeniedCategory; + + if (responseBodyPayload != null && mapper.hasField(responseBodyPayload, "channels")) { + Iterator it = mapper.getArrayIterator(responseBodyPayload, "channels"); + while (it.hasNext()) { + JsonElement objNode = it.next(); + affectedChannels.add(mapper.elementToString(objNode)); + } + } + + if (responseBodyPayload != null && mapper.hasField(responseBodyPayload, "channel-groups")) { + Iterator it = mapper.getArrayIterator(responseBodyPayload, "channel-groups"); + while (it.hasNext()) { + JsonElement objNode = it.next(); + String channelGroupName = + mapper.elementToString(objNode).substring(0, 1).equals(":") + ? mapper.elementToString(objNode).substring(1) + : mapper.elementToString(objNode); + affectedChannelGroups.add(channelGroupName); + } + } + + } + + if (response.code() == HttpURLConnection.HTTP_BAD_REQUEST) { + pnStatusCategory = PNStatusCategory.PNBadRequestCategory; + } + + callback.onResponse(null, + createStatusResponse(pnStatusCategory, response, ex, affectedChannels, + affectedChannelGroups)); + return; + } + storeRequestLatency(response, getOperationType()); + + try { + callbackResponse = createResponse(response); + } catch (PubNubException pubnubException) { + callback.onResponse(null, + createStatusResponse(PNStatusCategory.PNMalformedResponseCategory, response, + pubnubException, null, null)); + return; + } + + callback.onResponse(callbackResponse, + createStatusResponse(PNStatusCategory.PNAcknowledgmentCategory, response, + null, null, null)); + } + + @Override + public void onFailure(Call performedCall, Throwable throwable) { + if (silenceFailures) { + return; + } + + PNStatusCategory pnStatusCategory; + + PubNubException.PubNubExceptionBuilder pubnubException = PubNubException.builder() + .errormsg(throwable.getMessage()) + .cause(throwable); + + try { + throw throwable; + } catch (UnknownHostException networkException) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_CONNECTION_NOT_SET); + pnStatusCategory = PNStatusCategory.PNUnexpectedDisconnectCategory; + } catch (SocketException | SSLException exception) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_CONNECT_EXCEPTION); + pnStatusCategory = PNStatusCategory.PNUnexpectedDisconnectCategory; + } catch (SocketTimeoutException socketTimeoutException) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_SOCKET_TIMEOUT); + pnStatusCategory = PNStatusCategory.PNTimeoutCategory; + } catch (Throwable throwable1) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR); + if (performedCall.isCanceled()) { + pnStatusCategory = PNStatusCategory.PNCancelledCategory; + } else { + pnStatusCategory = PNStatusCategory.PNBadRequestCategory; + } + } + + callback.onResponse(null, + createStatusResponse(pnStatusCategory, null, pubnubException.build(), + null, null)); + + } + }); + } + + private PubNubException createPubNubException(Response response, + String responseBodyText, + JsonElement responseBody) { + if (response.code() == HttpURLConnection.HTTP_ENTITY_TOO_LARGE + || response.code() == HttpURLConnection.HTTP_REQ_TOO_LONG) { + return PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PAYLOAD_TOO_LARGE) + .affectedCall(call) + .statusCode(response.code()) + .jso(responseBody) + .errormsg(PubNubErrorBuilder.PNERROBJ_PAYLOAD_TOO_LARGE.getMessage()) + .build(); + } + + return PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR) + .errormsg(responseBodyText) + .jso(responseBody) + .statusCode(response.code()) + .affectedCall(call) + .build(); + } + + @Override + public void retry() { + silenceFailures = false; + async(cachedCallback); + } + + /** + * cancel the operation but do not alert anybody, useful for restarting the heartbeats and subscribe loops. + */ + @Override + public void silentCancel() { + if (call != null && !call.isCanceled()) { + this.silenceFailures = true; + call.cancel(); + } + } + + protected boolean isError(Response response) { + return response.code() != HttpURLConnection.HTTP_OK; + } + + private PNStatus createStatusResponse(PNStatusCategory category, Response response, Exception throwable, + ArrayList errorChannels, ArrayList errorChannelGroups) { + PNStatus.PNStatusBuilder pnStatus = PNStatus.builder(); + + pnStatus.executedEndpoint(this); + + if (response == null || throwable != null) { + pnStatus.error(true); + } + if (throwable != null) { + PNErrorData pnErrorData = new PNErrorData(throwable.getMessage(), throwable); + pnStatus.errorData(pnErrorData); + } + + if (response != null) { + pnStatus.statusCode(response.code()); + pnStatus.tlsEnabled(response.raw().request().url().isHttps()); + pnStatus.origin(response.raw().request().url().host()); + pnStatus.uuid(response.raw().request().url().queryParameter("uuid")); + pnStatus.authKey(response.raw().request().url().queryParameter(PubNubUtil.AUTH_QUERY_PARAM_NAME)); + pnStatus.clientRequest(response.raw().request()); + } + + pnStatus.operation(getOperationType()); + pnStatus.category(category); + + if (errorChannels != null && !errorChannels.isEmpty()) { + pnStatus.affectedChannels(errorChannels); + } else { + pnStatus.affectedChannels(getAffectedChannels()); + } + + if (errorChannelGroups != null && !errorChannelGroups.isEmpty()) { + pnStatus.affectedChannelGroups(errorChannelGroups); + } else { + pnStatus.affectedChannelGroups(getAffectedChannelGroups()); + } + + return pnStatus.build(); + } + + private void storeRequestLatency(Response response, PNOperationType type) { + if (this.telemetryManager != null) { + long latency = response.raw().receivedResponseAtMillis() - response.raw().sentRequestAtMillis(); + this.telemetryManager.storeLatency(latency, type); + } + } + + protected Map createBaseParams() { + Map params = new HashMap<>(); + + if (queryParam != null) { + params.putAll(queryParam); + } + + params.put("pnsdk", "PubNub-Java-Unified/".concat(this.pubnub.getVersion())); + params.put("uuid", this.pubnub.getConfiguration().getUuid()); + + if (this.pubnub.getConfiguration().isIncludeInstanceIdentifier()) { + params.put("instanceid", pubnub.getInstanceId()); + } + + if (this.pubnub.getConfiguration().isIncludeRequestIdentifier()) { + params.put("requestid", pubnub.getRequestId()); + } + + if (isAuthRequired()) { + final String token = tokenManager.getToken(); + if (token != null) { + params.put(PubNubUtil.AUTH_QUERY_PARAM_NAME, token); + } else if (this.pubnub.getConfiguration().getAuthKey() != null) { + params.put(PubNubUtil.AUTH_QUERY_PARAM_NAME, pubnub.getConfiguration().getAuthKey()); + } + } + + if (this.telemetryManager != null) { + params.putAll(this.telemetryManager.operationsLatency()); + } + + return params; + } + + protected Map encodeParams(Map params) { + Map encodedParams = new HashMap<>(params); + if (encodedParams.containsKey(PubNubUtil.AUTH_QUERY_PARAM_NAME)) { + encodedParams.put(PubNubUtil.AUTH_QUERY_PARAM_NAME, PubNubUtil.urlEncode(encodedParams.get(PubNubUtil.AUTH_QUERY_PARAM_NAME))); + } + return encodedParams; + } + + protected abstract List getAffectedChannels(); + + protected abstract List getAffectedChannelGroups(); + + protected abstract void validateParams() throws PubNubException; + + protected abstract Call doWork(Map baseParams) throws PubNubException; + + protected abstract Output createResponse(Response input) throws PubNubException; + + protected abstract PNOperationType getOperationType(); + + protected abstract boolean isAuthRequired(); + +} diff --git a/src/main/java/com/pubnub/api/endpoints/FetchMessages.java b/src/main/java/com/pubnub/api/endpoints/FetchMessages.java new file mode 100644 index 000000000..2a6f5ef74 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/FetchMessages.java @@ -0,0 +1,237 @@ +package com.pubnub.api.endpoints; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNBoundedPage; +import com.pubnub.api.models.consumer.history.PNFetchMessageItem; +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult; +import com.pubnub.api.models.server.FetchMessagesEnvelope; +import com.pubnub.api.vendor.Crypto; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS; + +@Slf4j +@Accessors(chain = true, fluent = true) +public class FetchMessages extends Endpoint { + private static final int SINGLE_CHANNEL_DEFAULT_MESSAGES = 100; + private static final int SINGLE_CHANNEL_MAX_MESSAGES = 100; + private static final int MULTIPLE_CHANNEL_DEFAULT_MESSAGES = 25; + private static final int MULTIPLE_CHANNEL_MAX_MESSAGES = 25; + private static final int DEFAULT_MESSAGES_WITH_ACTIONS = 25; + private static final int MAX_MESSAGES_WITH_ACTIONS = 25; + + @Setter + private List channels; + @Setter + private Integer maximumPerChannel; + @Setter + private Long start; + @Setter + private Long end; + + @Setter + private Boolean includeMeta; + @Setter + private Boolean includeMessageActions; + @Setter + private boolean includeMessageType = true; + @Setter + private boolean includeUUID = true; + + public FetchMessages(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channels == null || channels.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + + if (includeMeta == null) { + includeMeta = false; + } + + if (includeMessageActions == null) { + includeMessageActions = false; + } + + if (!includeMessageActions) { + if (channels.size() == 1) { + if (maximumPerChannel == null || maximumPerChannel < 1) { + maximumPerChannel = SINGLE_CHANNEL_DEFAULT_MESSAGES; + log.info("maximumPerChannel param defaulting to " + maximumPerChannel); + } else if (maximumPerChannel > SINGLE_CHANNEL_MAX_MESSAGES) { + maximumPerChannel = SINGLE_CHANNEL_MAX_MESSAGES; + log.info("maximumPerChannel param defaulting to " + maximumPerChannel); + } + } else { + if (maximumPerChannel == null || maximumPerChannel < 1) { + maximumPerChannel = MULTIPLE_CHANNEL_DEFAULT_MESSAGES; + log.info("maximumPerChannel param defaulting to " + maximumPerChannel); + } else if (maximumPerChannel > MULTIPLE_CHANNEL_MAX_MESSAGES) { + maximumPerChannel = MULTIPLE_CHANNEL_MAX_MESSAGES; + log.info("maximumPerChannel param defaulting to " + maximumPerChannel); + } + } + } else { + if (maximumPerChannel == null || maximumPerChannel < 1 || maximumPerChannel > MAX_MESSAGES_WITH_ACTIONS) { + maximumPerChannel = DEFAULT_MESSAGES_WITH_ACTIONS; + log.info("maximumPerChannel param defaulting to " + maximumPerChannel); + } + } + } + + @Override + protected Call doWork(Map params) throws PubNubException { + params.put("max", String.valueOf(maximumPerChannel)); + + if (start != null) { + params.put("start", Long.toString(start).toLowerCase()); + } + if (end != null) { + params.put("end", Long.toString(end).toLowerCase()); + } + + if (includeMeta) { + params.put("include_meta", String.valueOf(includeMeta)); + } + params.put("include_uuid", Boolean.toString(includeUUID)); + params.put("include_message_type", Boolean.toString(includeMessageType)); + + if (!includeMessageActions) { + return this.getRetrofit().getHistoryService().fetchMessages( + this.getPubnub().getConfiguration().getSubscribeKey(), PubNubUtil.joinString(channels, ","), + params); + } else { + if (channels.size() > 1) { + throw PubNubException.builder().pubnubError(PNERROBJ_HISTORY_MESSAGE_ACTIONS_MULTIPLE_CHANNELS).build(); + } + return this.getRetrofit().getHistoryService().fetchMessagesWithActions( + this.getPubnub().getConfiguration().getSubscribeKey(), channels.get(0), params); + } + } + + @Override + protected PNFetchMessagesResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + HashMap> channelsMap = new HashMap<>(); + + for (Map.Entry> entry : input.body().getChannels().entrySet()) { + List items = new ArrayList<>(); + + for (PNFetchMessageItem item : entry.getValue()) { + PNFetchMessageItem.PNFetchMessageItemBuilder messageItemBuilder = item.toBuilder(); + + messageItemBuilder.message(processMessage(item.getMessage())); + if (includeMessageActions) { + if (item.getActions() != null) { + messageItemBuilder.actions(item.getActions()); + } else { + messageItemBuilder.actions(new HashMap<>()); + } + } else { + messageItemBuilder.actions(null); + } + items.add(messageItemBuilder.build()); + } + + channelsMap.put(entry.getKey(), items); + } + + PNBoundedPage page = null; + FetchMessagesEnvelope.FetchMessagesPage more = input.body().getMore(); + if (more != null) { + page = new PNBoundedPage(more.getStart(), more.getEnd(), more.getMax()); + } + + return PNFetchMessagesResult.builder() + .channels(channelsMap) + .page(page) + .build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFetchMessagesOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + private JsonElement processMessage(JsonElement message) throws PubNubException { + // if we do not have a crypto key, there is no way to process the node; let's return. + if (this.getPubnub().getConfiguration().getCipherKey() == null) { + return message; + } + + Crypto crypto = new Crypto(this.getPubnub().getConfiguration().getCipherKey(), + this.getPubnub().getConfiguration().isUseRandomInitializationVector()); + MapperManager mapper = this.getPubnub().getMapper(); + String inputText; + String outputText; + JsonElement outputObject; + + if (mapper.isJsonObject(message) && mapper.hasField(message, "pn_other")) { + inputText = mapper.elementToString(message, "pn_other"); + } else { + inputText = mapper.elementToString(message); + } + + outputText = crypto.decrypt(inputText); + outputObject = mapper.fromJson(outputText, JsonElement.class); + + // inject the decoded response into the payload + if (mapper.isJsonObject(message) && mapper.hasField(message, "pn_other")) { + JsonObject objectNode = mapper.getAsObject(message); + mapper.putOnObject(objectNode, "pn_other", outputObject); + outputObject = objectNode; + } + + return outputObject; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/History.java b/src/main/java/com/pubnub/api/endpoints/History.java new file mode 100644 index 000000000..2a8df8438 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/History.java @@ -0,0 +1,203 @@ +package com.pubnub.api.endpoints; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.history.PNHistoryItemResult; +import com.pubnub.api.models.consumer.history.PNHistoryResult; +import com.pubnub.api.vendor.Crypto; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class History extends Endpoint { + private static final int MAX_COUNT = 100; + @Setter + private String channel; + @Setter + private Long start; + @Setter + private Long end; + @Setter + private Boolean reverse; + @Setter + private Integer count; + @Setter + private Boolean includeTimetoken; + @Setter + private Boolean includeMeta; + + public History(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (includeMeta == null) { + includeMeta = false; + } + if (includeTimetoken == null) { + includeTimetoken = false; + } + } + + @Override + protected Call doWork(Map params) { + + if (reverse != null) { + params.put("reverse", String.valueOf(reverse)); + } + + if (includeTimetoken != null) { + params.put("include_token", String.valueOf(includeTimetoken)); + } + + if (includeMeta) { + params.put("include_meta", String.valueOf(includeMeta)); + } + + if (count != null && count > 0 && count <= MAX_COUNT) { + params.put("count", String.valueOf(count)); + } else { + params.put("count", "100"); + } + + if (start != null) { + params.put("start", Long.toString(start).toLowerCase()); + } + if (end != null) { + params.put("end", Long.toString(end).toLowerCase()); + } + + return this.getRetrofit().getHistoryService().fetchHistory(this.getPubnub().getConfiguration() + .getSubscribeKey(), channel, params); + } + + @Override + protected PNHistoryResult createResponse(Response input) throws PubNubException { + PNHistoryResult.PNHistoryResultBuilder historyData = PNHistoryResult.builder(); + List messages = new ArrayList<>(); + MapperManager mapper = getPubnub().getMapper(); + + if (input.body() != null) { + Long startTimeToken = mapper.elementToLong(mapper.getArrayElement(input.body(), 1)); + Long endTimeToken = mapper.elementToLong(mapper.getArrayElement(input.body(), 2)); + + historyData.startTimetoken(startTimeToken); + historyData.endTimetoken(endTimeToken); + + + if (mapper.getArrayElement(input.body(), 0).isJsonArray()) { + Iterator it = mapper.getArrayIterator(mapper.getArrayElement(input.body(), 0)); + while (it.hasNext()) { + JsonElement historyEntry = it.next(); + PNHistoryItemResult.PNHistoryItemResultBuilder historyItem = PNHistoryItemResult.builder(); + JsonElement message; + + if (includeTimetoken || includeMeta) { + message = processMessage(mapper.getField(historyEntry, "message")); + if (includeTimetoken) { + historyItem.timetoken(mapper.elementToLong(historyEntry, "timetoken")); + } + if (includeMeta) { + historyItem.meta(mapper.getField(historyEntry, "meta")); + } + } else { + message = processMessage(historyEntry); + } + + historyItem.entry(message); + messages.add(historyItem.build()); + } + } else { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR) + .errormsg("History is disabled") + .jso(input.body()) + .build(); + } + + + historyData.messages(messages); + } + + return historyData.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNHistoryOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + private JsonElement processMessage(JsonElement message) throws PubNubException { + // if we do not have a crypto key, there is no way to process the node; let's return. + if (this.getPubnub().getConfiguration().getCipherKey() == null) { + return message; + } + + Crypto crypto = new Crypto(this.getPubnub().getConfiguration().getCipherKey(), this.getPubnub().getConfiguration().isUseRandomInitializationVector()); + MapperManager mapper = getPubnub().getMapper(); + String inputText; + String outputText; + JsonElement outputObject; + + if (mapper.isJsonObject(message) && mapper.hasField(message, "pn_other")) { + inputText = mapper.elementToString(message, "pn_other"); + } else { + inputText = mapper.elementToString(message); + } + + outputText = crypto.decrypt(inputText); + outputObject = this.getPubnub().getMapper().fromJson(outputText, JsonElement.class); + + // inject the decoded response into the payload + if (mapper.isJsonObject(message) && mapper.hasField(message, "pn_other")) { + JsonObject objectNode = mapper.getAsObject(message); + mapper.putOnObject(objectNode, "pn_other", outputObject); + outputObject = objectNode; + } + + return outputObject; + } + +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/endpoints/MessageCounts.java b/src/main/java/com/pubnub/api/endpoints/MessageCounts.java new file mode 100644 index 000000000..fb99c72c7 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/MessageCounts.java @@ -0,0 +1,127 @@ +package com.pubnub.api.endpoints; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.history.PNMessageCountResult; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class MessageCounts extends Endpoint { + + /** + * The channel name you wish to pull history from. May be a single channel, or multiple channels, separated by + * comma. + */ + @Setter + private List channels; + + /** + * Comma-delimited list of timetokens, in order of the channels list, in the request path. If list of timetokens + * is not same length as list of channels, a 400 bad request will result. + */ + @Setter + private List channelsTimetoken; + + public MessageCounts(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + + if (channels == null || channels.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if ((channelsTimetoken == null || channelsTimetoken.isEmpty()) || channelsTimetoken.contains(null)) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_TIMETOKEN_MISSING).build(); + } + if (channelsTimetoken.size() != channels.size() && channelsTimetoken.size() > 1) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNELS_TIMETOKEN_MISMATCH) + .build(); + } + } + + @Override + protected Call doWork(Map params) { + + if (channelsTimetoken.size() == 1) { + params.put("timetoken", PubNubUtil.joinLong(channelsTimetoken, ",")); + } else { + params.put("channelsTimetoken", PubNubUtil.joinLong(channelsTimetoken, ",")); + } + + return this.getRetrofit() + .getHistoryService() + .fetchCount(this.getPubnub().getConfiguration().getSubscribeKey(), + PubNubUtil.joinString(channels, ","), params); + } + + @Override + protected PNMessageCountResult createResponse(Response input) throws PubNubException { + + PNMessageCountResult.PNMessageCountResultBuilder messageCountsData = PNMessageCountResult.builder(); + HashMap channelsMap = new HashMap<>(); + + MapperManager mapper = getPubnub().getMapper(); + + if (input.body() != null) { + + if (mapper.isJsonObject(input.body()) && mapper.hasField(input.body(), "channels")) { + Iterator> it = mapper.getObjectIterator(input.body(), "channels"); + while (it.hasNext()) { + Map.Entry entry = it.next(); + channelsMap.put(entry.getKey(), entry.getValue().getAsLong()); + } + messageCountsData.channels(channelsMap); + } else { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR) + .errormsg("History is disabled") + .jso(input.body()) + .build(); + } + } + + return messageCountsData.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNMessageCountOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/Time.java b/src/main/java/com/pubnub/api/endpoints/Time.java new file mode 100644 index 000000000..74f7dfbf6 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/Time.java @@ -0,0 +1,69 @@ +package com.pubnub.api.endpoints; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNTimeResult; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.List; +import java.util.Map; + +public class Time extends Endpoint, PNTimeResult> { + + public Time(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + + @Override + protected void validateParams() throws PubNubException { + + } + + @Override + protected Call> doWork(Map params) { + return this.getRetrofit().getTimeService().fetchTime(params); + } + + @Override + protected PNTimeResult createResponse(Response> input) throws PubNubException { + PNTimeResult.PNTimeResultBuilder timeData = PNTimeResult.builder(); + + if (input.body() == null || input.body().size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + timeData.timetoken(input.body().get(0)); + return timeData.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNTimeOperation; + } + + @Override + protected boolean isAuthRequired() { + return false; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/access/Grant.java b/src/main/java/com/pubnub/api/endpoints/access/Grant.java new file mode 100644 index 000000000..d23bb6847 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/access/Grant.java @@ -0,0 +1,238 @@ +package com.pubnub.api.endpoints.access; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData; +import com.pubnub.api.models.server.Envelope; +import com.pubnub.api.models.server.access_manager.AccessManagerGrantPayload; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +@Accessors(chain = true, fluent = true) +public class Grant extends Endpoint, PNAccessManagerGrantResult> { + + @Setter + private boolean read; + @Setter + private boolean write; + @Setter + private boolean manage; + @Setter + private boolean delete; + @Setter + private boolean get; + @Setter + private boolean update; + @Setter + private boolean join; + @Setter + private Integer ttl; + + @Setter + private List authKeys; + @Setter + private List channels; + @Setter + private List channelGroups; + @Setter + private List uuids = Collections.emptyList(); + + public Grant(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + authKeys = new ArrayList<>(); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSecretKey() == null || this.getPubnub() + .getConfiguration() + .getSecretKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SECRET_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getPublishKey() == null || this.getPubnub() + .getConfiguration() + .getPublishKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUBLISH_KEY_MISSING).build(); + } + if ((!channels.isEmpty() || !channelGroups.isEmpty()) && !uuids.isEmpty()) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("Grants for channels or channelGroups can't be changed together with grants for UUIDs") + .build(); + } + if (!uuids.isEmpty() && authKeys.isEmpty()) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("UUIDs grant management require providing non empty authKeys") + .build(); + } + } + + @Override + protected Call> doWork(Map queryParams) throws PubNubException { + + if (channels.size() > 0) { + queryParams.put("channel", PubNubUtil.joinString(channels, ",")); + } + + if (channelGroups.size() > 0) { + queryParams.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + if (uuids.size() > 0) { + queryParams.put("target-uuid", PubNubUtil.joinString(uuids, ",")); + } + + if (authKeys.size() > 0) { + queryParams.put("auth", PubNubUtil.joinString(authKeys, ",")); + } + + if (ttl != null && ttl >= -1) { + queryParams.put("ttl", String.valueOf(ttl)); + } + + queryParams.put("r", (read) ? "1" : "0"); + queryParams.put("w", (write) ? "1" : "0"); + queryParams.put("m", (manage) ? "1" : "0"); + queryParams.put("d", (delete) ? "1" : "0"); + queryParams.put("g", get ? "1" : "0"); + queryParams.put("u", update ? "1" : "0"); + queryParams.put("j", join ? "1" : "0"); + + return this.getRetrofit() + .getAccessManagerService() + .grant(this.getPubnub().getConfiguration().getSubscribeKey(), queryParams); + } + + @Override + protected PNAccessManagerGrantResult createResponse(Response> input) throws + PubNubException { + MapperManager mapperManager = getPubnub().getMapper(); + PNAccessManagerGrantResult.PNAccessManagerGrantResultBuilder pnAccessManagerGrantResult = + PNAccessManagerGrantResult.builder(); + + if (input.body() == null || input.body().getPayload() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + AccessManagerGrantPayload data = input.body().getPayload(); + Map> constructedChannels = new HashMap<>(); + Map> constructedGroups = new HashMap<>(); + + // we have a case of a singular channel. + if (data.getChannel() != null) { + constructedChannels.put(data.getChannel(), data.getAuthKeys()); + } + + if (channelGroups != null) { + if (channelGroups.size() == 1) { + constructedGroups.put(mapperManager.elementToString(data.getChannelGroups()), data.getAuthKeys()); + } else if (channelGroups.size() > 1) { + Iterator> it = mapperManager.getObjectIterator(data.getChannelGroups()); + while (it.hasNext()) { + Map.Entry channelGroup = it.next(); + constructedGroups.put(channelGroup.getKey(), createKeyMap(channelGroup.getValue())); + } + } + } + + if (data.getChannels() != null) { + for (String fetchedChannel : data.getChannels().keySet()) { + constructedChannels.put(fetchedChannel, data.getChannels().get(fetchedChannel).getAuthKeys()); + } + } + + Map> constructedUuids = new HashMap<>(); + + if (data.getUuids() != null) { + for (String fetchedUuid : data.getUuids().keySet()) { + constructedUuids.put(fetchedUuid, data.getUuids().get(fetchedUuid).getAuthKeys()); + } + } + + return pnAccessManagerGrantResult + .subscribeKey(data.getSubscribeKey()) + .level(data.getLevel()) + .ttl(data.getTtl()) + .channels(constructedChannels) + .channelGroups(constructedGroups) + .uuids(constructedUuids) + .build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNAccessManagerGrant; + } + + @Override + protected boolean isAuthRequired() { + return false; + } + + private Map createKeyMap(JsonElement input) { + Map result = new HashMap<>(); + MapperManager mapper = getPubnub().getMapper(); + + Iterator> it = mapper.getObjectIterator(input, "auths"); + while (it.hasNext()) { + Map.Entry keyMap = it.next(); + PNAccessManagerKeyData pnAccessManagerKeyData = PNAccessManagerKeyData.builder() + .manageEnabled(mapper.getAsBoolean(keyMap.getValue(), "m")) + .writeEnabled(mapper.getAsBoolean(keyMap.getValue(), "w")) + .readEnabled(mapper.getAsBoolean(keyMap.getValue(), "r")) + .deleteEnabled(mapper.getAsBoolean(keyMap.getValue(), "d")) + .getEnabled(mapper.getAsBoolean(keyMap.getValue(), "g")) + .updateEnabled(mapper.getAsBoolean(keyMap.getValue(), "u")) + .joinEnabled(mapper.getAsBoolean(keyMap.getValue(), "j")).build(); + + result.put(keyMap.getKey(), pnAccessManagerKeyData); + } + + return result; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java b/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java new file mode 100644 index 000000000..074309792 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java @@ -0,0 +1,132 @@ +package com.pubnub.api.endpoints.access; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult; +import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant; +import com.pubnub.api.models.server.access_manager.v3.GrantTokenRequestBody; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.PubNubUtil.isNullOrEmpty; + +@Accessors(chain = true, fluent = true) +public class GrantToken extends Endpoint { + + @Setter + private Integer ttl; + @Setter + private Object meta; + @Setter + private String authorizedUUID; + @Setter + private List channels = Collections.emptyList(); + @Setter + private List channelGroups = Collections.emptyList(); + @Setter + private List uuids = Collections.emptyList(); + + public GrantToken(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + final ArrayList affectedChannels = new ArrayList<>(); + for (ChannelGrant channelGrant : channels) { + affectedChannels.add(channelGrant.getId()); + } + return affectedChannels; + } + + @Override + protected List getAffectedChannelGroups() { + final ArrayList affectedChannelGroups = new ArrayList<>(); + for (ChannelGroupGrant channelGroupGrant : channelGroups) { + affectedChannelGroups.add(channelGroupGrant.getId()); + } + return affectedChannelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSecretKey() == null || this.getPubnub() + .getConfiguration() + .getSecretKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SECRET_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (isNullOrEmpty(channels) + && isNullOrEmpty(channelGroups) + && isNullOrEmpty(uuids)) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_RESOURCES_MISSING) + .build(); + } + if (this.ttl == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_TTL_MISSING) + .build(); + } + } + + @Override + protected Call doWork(Map queryParams) throws PubNubException { + GrantTokenRequestBody requestBody = GrantTokenRequestBody.builder() + .ttl(ttl) + .channels(channels) + .groups(channelGroups) + .uuids(uuids) + .meta(meta) + .uuid(authorizedUUID) + .build(); + + return this.getRetrofit() + .getAccessManagerService() + .grantToken(this.getPubnub().getConfiguration().getSubscribeKey(), requestBody, queryParams); + } + + @Override + protected PNGrantTokenResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + return null; + } + + return new PNGrantTokenResult(input.body().getAsJsonObject("data").get("token").getAsString()); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNAccessManagerGrantToken; + } + + @Override + protected boolean isAuthRequired() { + return false; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.java b/src/main/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.java new file mode 100644 index 000000000..11214ba62 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroup.java @@ -0,0 +1,88 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAddChannelResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class AddChannelChannelGroup extends Endpoint { + @Setter + private String channelGroup; + @Setter + private List channels; + + + public AddChannelChannelGroup(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.singletonList(channelGroup); + } + + @Override + protected void validateParams() throws PubNubException { + if (channelGroup == null || channelGroup.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_GROUP_MISSING).build(); + } + if (channels.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) { + if (channels.size() > 0) { + params.put("add", PubNubUtil.joinString(channels, ",")); + } + + return this.getRetrofit().getChannelGroupService().addChannelChannelGroup(this.getPubnub().getConfiguration().getSubscribeKey(), channelGroup, params); + } + + @Override + protected PNChannelGroupsAddChannelResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + return PNChannelGroupsAddChannelResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNAddChannelsToGroupOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.java b/src/main/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.java new file mode 100644 index 000000000..fd058359a --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroup.java @@ -0,0 +1,83 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAllChannelsResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class AllChannelsChannelGroup extends Endpoint, PNChannelGroupsAllChannelsResult> { + @Setter + private String channelGroup; + + public AllChannelsChannelGroup(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (channelGroup == null || channelGroup.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_GROUP_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) { + return this.getRetrofit().getChannelGroupService() + .allChannelsChannelGroup(this.getPubnub().getConfiguration().getSubscribeKey(), channelGroup, params); + } + + @Override + protected PNChannelGroupsAllChannelsResult createResponse(Response> input) throws PubNubException { + Map stateMappings; + + if (input.body() == null || input.body().getPayload() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + stateMappings = (Map) input.body().getPayload(); + List channels = (ArrayList) stateMappings.get("channels"); + + return PNChannelGroupsAllChannelsResult.builder() + .channels(channels) + .build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNChannelsForGroupOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.java b/src/main/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.java new file mode 100644 index 000000000..b2657f145 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroup.java @@ -0,0 +1,75 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class DeleteChannelGroup extends Endpoint { + @Setter + private String channelGroup; + + public DeleteChannelGroup(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.singletonList(channelGroup); + } + + @Override + protected void validateParams() throws PubNubException { + if (channelGroup == null || channelGroup.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_GROUP_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) { + return this.getRetrofit().getChannelGroupService() + .deleteChannelGroup(this.getPubnub().getConfiguration().getSubscribeKey(), channelGroup, params); + } + + @Override + protected PNChannelGroupsDeleteGroupResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + return PNChannelGroupsDeleteGroupResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveGroupOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.java b/src/main/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.java new file mode 100644 index 000000000..f6e1bb1b5 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroup.java @@ -0,0 +1,77 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult; +import com.pubnub.api.models.server.Envelope; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class ListAllChannelGroup extends Endpoint, PNChannelGroupsListAllResult> { + + public ListAllChannelGroup(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + } + + @Override + protected Call> doWork(Map params) { + return this.getRetrofit().getChannelGroupService() + .listAllChannelGroup(this.getPubnub().getConfiguration().getSubscribeKey(), params); + } + + @Override + protected PNChannelGroupsListAllResult createResponse(Response> input) throws PubNubException { + Map stateMappings; + + if (input.body() == null || input.body().getPayload() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + stateMappings = (Map) input.body().getPayload(); + List groups = (ArrayList) stateMappings.get("groups"); + + return PNChannelGroupsListAllResult.builder() + .groups(groups) + .build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNChannelGroupsOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.java b/src/main/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.java new file mode 100644 index 000000000..4404a1a26 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroup.java @@ -0,0 +1,88 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class RemoveChannelChannelGroup extends Endpoint { + @Setter + private String channelGroup; + @Setter + private List channels; + + + public RemoveChannelChannelGroup(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.singletonList(channelGroup); + } + + @Override + protected void validateParams() throws PubNubException { + if (channelGroup == null || channelGroup.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_GROUP_MISSING).build(); + } + if (channels.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) { + if (channels.size() > 0) { + params.put("remove", PubNubUtil.joinString(channels, ",")); + } + + return this.getRetrofit().getChannelGroupService() + .removeChannel(this.getPubnub().getConfiguration().getSubscribeKey(), channelGroup, params); + } + + @Override + protected PNChannelGroupsRemoveChannelResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + return PNChannelGroupsRemoveChannelResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveChannelsFromGroupOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/DeleteFile.java b/src/main/java/com/pubnub/api/endpoints/files/DeleteFile.java new file mode 100644 index 000000000..6eacb8333 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/DeleteFile.java @@ -0,0 +1,107 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.ChannelFileNameFileIdBuilder; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.files.PNDeleteFileResult; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DeleteFile extends Endpoint { + private final String channel; + private final String fileName; + private final String fileId; + + public DeleteFile(String channel, + String fileName, + String fileId, + PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.channel = channel; + this.fileName = fileName; + this.fileId = fileId; + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.emptyList(); + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + return getRetrofit().getFilesService().deleteFile(getPubnub().getConfiguration().getSubscribeKey(), + channel, + fileId, + fileName, + baseParams); + } + + @Override + protected PNDeleteFileResult createResponse(Response input) throws PubNubException { + if (input.isSuccessful()) { + return new PNDeleteFileResult(input.code()); + } else { + throw PubNubException.builder() + .statusCode(input.code()) + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .errormsg("File deletion have failed") + .build(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + public static class Builder extends ChannelFileNameFileIdBuilder { + private Builder(ChannelStep>> builder) { + super(builder); + } + } + + public static Builder builder(PubNub pubNub, + TelemetryManager telemetryManager, + RetrofitManager retrofitManager, + TokenManager tokenManager) { + return new Builder(ChannelFileNameFileIdBuilder.create((channel, fileName, fileId) -> + new DeleteFile(channel, fileName, fileId, pubNub, telemetryManager, retrofitManager, tokenManager))); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/DownloadFile.java b/src/main/java/com/pubnub/api/endpoints/files/DownloadFile.java new file mode 100644 index 000000000..ae8411f7e --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/DownloadFile.java @@ -0,0 +1,122 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.files.requiredparambuilder.ChannelFileNameFileIdBuilder; +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.files.PNDownloadFileResult; +import com.pubnub.api.vendor.FileEncryptionUtil; +import lombok.Setter; +import lombok.experimental.Accessors; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Response; + +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.vendor.FileEncryptionUtil.effectiveCipherKey; + +@Accessors(chain = true, fluent = true) +public class DownloadFile extends Endpoint { + private final String channel; + private final String fileName; + private final String fileId; + + @Setter + private String cipherKey; + + public DownloadFile(String channel, + String fileName, + String fileId, + PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.channel = channel; + this.fileName = fileName; + this.fileId = fileId; + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.emptyList(); + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + return getRetrofit().getFilesService().downloadFile(getPubnub().getConfiguration().getSubscribeKey(), + channel, + fileId, + fileName, + baseParams); + } + + @Override + protected PNDownloadFileResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .build(); + } + String effectiveCipherKey = effectiveCipherKey(getPubnub(), cipherKey); + if (effectiveCipherKey == null) { + return new PNDownloadFileResult(fileName, input.body().byteStream()); + } else { + InputStream decryptedByteStream = FileEncryptionUtil.decrypt(effectiveCipherKey, input.body().byteStream()); + return new PNDownloadFileResult(fileName, decryptedByteStream); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + public static class Builder extends ChannelFileNameFileIdBuilder { + private Builder(ChannelStep>> builder) { + super(builder); + } + } + + public static Builder builder(PubNub pubNub, + TelemetryManager telemetryManager, + RetrofitManager retrofitManager, + TokenManager tokenManager) { + return new Builder(ChannelFileNameFileIdBuilder.create((channel, fileName, fileId) -> + new DownloadFile(channel, fileName, fileId, pubNub, telemetryManager, retrofitManager, tokenManager))); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/GenerateUploadUrl.java b/src/main/java/com/pubnub/api/endpoints/files/GenerateUploadUrl.java new file mode 100644 index 000000000..ad3420fc8 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/GenerateUploadUrl.java @@ -0,0 +1,136 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.remoteaction.RemoteAction; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.files.FileUploadRequestDetails; +import com.pubnub.api.models.server.files.FormField; +import com.pubnub.api.models.server.files.GenerateUploadUrlPayload; +import com.pubnub.api.models.server.files.GeneratedUploadUrlResponse; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +class GenerateUploadUrl extends Endpoint { + private final String channel; + private final String fileName; + + GenerateUploadUrl(String channel, + String fileName, + PubNub pubNub, + TelemetryManager telemetryManager, + RetrofitManager retrofitManager, TokenManager tokenManager) { + super(pubNub, telemetryManager, retrofitManager, tokenManager); + this.channel = channel; + this.fileName = fileName; + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.emptyList(); + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected FileUploadRequestDetails createResponse(Response input) throws + PubNubException { + if (input != null && input.body() != null) { + FormField keyFormField = getKeyFormField(input.body()); + + GeneratedUploadUrlResponse response = input.body(); + return new FileUploadRequestDetails( + response.getStatus(), + response.getData(), + response.getFileUploadRequest().getUrl(), + response.getFileUploadRequest().getMethod(), + response.getFileUploadRequest().getExpirationDate(), + keyFormField, + response.getFileUploadRequest().getFormFields() + ); + } else { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .build(); + } + } + + private FormField getKeyFormField(GeneratedUploadUrlResponse response) throws PubNubException { + List formFields = response.getFileUploadRequest().getFormFields(); + FormField found = null; + for (FormField formField : formFields) { + if (formField.getKey().equals("key")) { + found = formField; + } + } + if (found == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .errormsg("GenerateUploadUrl response do not contain \"key\" form param") + .build(); + } + return found; + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + @Override + protected Call doWork(Map baseParams) { + return getRetrofit().getFilesService().generateUploadUrl(getPubnub().getConfiguration().getSubscribeKey(), + channel, + new GenerateUploadUrlPayload(fileName), + baseParams); + } + + static class Factory { + private final PubNub pubNub; + private final TelemetryManager telemetryManager; + private final RetrofitManager retrofitManager; + private final TokenManager tokenManager; + + Factory(PubNub pubNub, TelemetryManager telemetryManager, RetrofitManager retrofitManager, TokenManager tokenManager) { + + this.pubNub = pubNub; + this.telemetryManager = telemetryManager; + this.retrofitManager = retrofitManager; + this.tokenManager = tokenManager; + } + + RemoteAction create(String channel, String fileName) { + return new GenerateUploadUrl(channel, fileName, pubNub, telemetryManager, retrofitManager, tokenManager); + } + + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/GetFileUrl.java b/src/main/java/com/pubnub/api/endpoints/files/GetFileUrl.java new file mode 100644 index 000000000..76f0f8a94 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/GetFileUrl.java @@ -0,0 +1,151 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.ChannelFileNameFileIdBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.files.PNFileUrlResult; +import okhttp3.Request; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Response; + +import java.net.HttpURLConnection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +public class GetFileUrl extends Endpoint { + + private final String channel; + private final String fileId; + private final String fileName; + private PNCallback cachedCallback; + private final ExecutorService executorService; + + public GetFileUrl(String channel, + String fileName, + String fileId, + PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.channel = channel; + this.fileId = fileId; + this.fileName = fileName; + this.executorService = retrofitInstance.getTransactionClientExecutorService(); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return Collections.emptyList(); + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + public PNFileUrlResult sync() throws PubNubException { + try { + Map baseParams = createBaseParams(); + Call call = getRetrofit().getFilesService() + .downloadFile(getPubnub().getConfiguration().getSubscribeKey(), + channel, + fileId, + fileName, + baseParams); + Request signedRequest = PubNubUtil.signRequest(call.request(), + getPubnub().getConfiguration(), + getPubnub().getTimestamp()); + return new PNFileUrlResult(signedRequest.url().toString()); + } catch (Exception e) { + throw PubNubException.builder().cause(e).build(); + } + } + + @Override + public void async(@NotNull PNCallback callback) { + this.cachedCallback = callback; + executorService.execute(() -> { + try { + PNFileUrlResult res = sync(); + callback.onResponse(res, PNStatus.builder().statusCode(HttpURLConnection.HTTP_OK).build()); + } catch (PubNubException ex) { + callback.onResponse(null, PNStatus.builder() + .statusCode(HttpURLConnection.HTTP_INTERNAL_ERROR) + .errorData(new PNErrorData(ex.getErrormsg(), ex)) + .error(true) + .build()); + } + }); + + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + throw PubNubException.builder().build(); + } + + + @Override + protected PNFileUrlResult createResponse(Response input) throws PubNubException { + throw PubNubException.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + public void retry() { + async(cachedCallback); + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + public static class Builder extends ChannelFileNameFileIdBuilder { + private Builder(ChannelStep>> builder) { + super(builder); + } + } + + public static Builder builder(PubNub pubNub, + TelemetryManager telemetryManager, + RetrofitManager retrofitManager, + TokenManager tokenManager) { + return new Builder(ChannelFileNameFileIdBuilder.create((channel, fileName, fileId) -> + new GetFileUrl(channel, fileName, fileId, pubNub, telemetryManager, retrofitManager, tokenManager))); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/ListFiles.java b/src/main/java/com/pubnub/api/endpoints/files/ListFiles.java new file mode 100644 index 000000000..52f56d857 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/ListFiles.java @@ -0,0 +1,153 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNPage; +import com.pubnub.api.models.consumer.files.PNListFilesResult; +import com.pubnub.api.models.server.files.ListFilesResult; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class ListFiles extends Endpoint { + + private static final String LIMIT_QUERY_PARAM = "limit"; + private static final String NEXT_PAGE_QUERY_PARAM = "next"; + private static final String DEFAULT_LIMIT = "100"; + private static final int MIN_LIMIT = 1; + private static final int MAX_LIMIT = 100; + + private final String channel; + @Setter + private Integer limit; + @Setter + private PNPage.Next next; + + public ListFiles(String channel, + PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.channel = channel; + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + + if (limit != null && !(MIN_LIMIT <= limit && limit <= MAX_LIMIT)) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("Limit should be in range from 1 to 100 (both inclusive)") + .build(); + } + + if (next != null && (next.getHash() == null || next.getHash().isEmpty())) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("Next should not be an empty string") + .build(); + } + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + HashMap allParams = new HashMap<>(baseParams); + + if (limit != null) { + allParams.put(LIMIT_QUERY_PARAM, limit.toString()); + } else { + allParams.put(LIMIT_QUERY_PARAM, DEFAULT_LIMIT); + } + + if (next != null) { + allParams.put(NEXT_PAGE_QUERY_PARAM, PubNubUtil.urlEncode(next.getHash())); + } + + return getRetrofit().getFilesService().listFiles(getPubnub().getConfiguration().getSubscribeKey(), + channel, + encodeParams(allParams)); + } + + @Override + protected PNListFilesResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .build(); + } + + return new PNListFilesResult(input.body().getCount(), + PNPage.next(input.body().getNext()), + input.body().getStatus(), + input.body().getData() + ); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + public static class Builder implements BuilderSteps.ChannelStep { + + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + public Builder(PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + + this.pubnubInstance = pubnubInstance; + this.telemetry = telemetry; + this.retrofitInstance = retrofitInstance; + this.tokenManager = tokenManager; + } + + @Override + public ListFiles channel(String channel) { + return new ListFiles(channel, pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java b/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java new file mode 100644 index 000000000..583d3f7aa --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java @@ -0,0 +1,159 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.ChannelFileNameFileIdBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.files.PNBaseFile; +import com.pubnub.api.models.consumer.files.PNPublishFileMessageResult; +import com.pubnub.api.models.server.files.FileUploadNotification; +import com.pubnub.api.services.FilesService; +import com.pubnub.api.vendor.Crypto; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class PublishFileMessage extends Endpoint, PNPublishFileMessageResult> { + + @Setter + private Object message; + @Setter + private Object meta; + @Setter + private Integer ttl; + @Setter + private Boolean shouldStore; + private final String channel; + private final PNBaseFile pnFile; + private final FilesService filesService; + private final MapperManager mapper; + private final PNConfiguration configuration; + + public PublishFileMessage(String channel, + String fileName, + String fileId, + PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.channel = channel; + this.pnFile = new PNBaseFile(fileId, fileName); + this.filesService = retrofitInstance.getFilesService(); + this.mapper = pubnubInstance.getMapper(); + this.configuration = pubnubInstance.getConfiguration(); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map baseParams) throws PubNubException { + String stringifiedMessage = mapper.toJsonUsinJackson(new FileUploadNotification(this.message, pnFile)); + String messageAsString; + if (getPubnub().getConfiguration().getCipherKey() != null) { + Crypto crypto = new Crypto(getPubnub().getConfiguration().getCipherKey(), getPubnub().getConfiguration().isUseRandomInitializationVector()); + messageAsString = "\"".concat(crypto.encrypt(stringifiedMessage)).concat("\""); + } else { + messageAsString = PubNubUtil.urlEncode(stringifiedMessage); + } + + final HashMap params = new HashMap<>(baseParams); + + if (meta != null) { + String stringifiedMeta = mapper.toJsonUsinJackson(meta); + stringifiedMeta = PubNubUtil.urlEncode(stringifiedMeta); + params.put("meta", stringifiedMeta); + } + + if (shouldStore != null) { + if (shouldStore) { + params.put("store", "1"); + } else { + params.put("store", "0"); + } + } + + if (ttl != null) { + params.put("ttl", String.valueOf(ttl)); + } + + return filesService.notifyAboutFileUpload(configuration.getPublishKey(), + configuration.getSubscribeKey(), + channel, + messageAsString, + params); + } + + @Override + protected PNPublishFileMessageResult createResponse(Response> input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_INTERNAL_ERROR) + .build(); + } + long timetoken = Long.parseLong(input.body().get(2).toString()); + return new PNPublishFileMessageResult(timetoken); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + public static class Builder + extends ChannelFileNameFileIdBuilder { + private Builder(ChannelStep>> builder) { + super(builder); + } + } + + public static Builder builder(PubNub pubNub, + TelemetryManager telemetryManager, + RetrofitManager retrofitManager, + TokenManager tokenManager) { + return new Builder(ChannelFileNameFileIdBuilder.create((channel, fileName, fileId) -> + new PublishFileMessage(channel, fileName, fileId, pubNub, telemetryManager, retrofitManager, tokenManager))); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/SendFile.java b/src/main/java/com/pubnub/api/endpoints/files/SendFile.java new file mode 100644 index 000000000..ab4012698 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/SendFile.java @@ -0,0 +1,278 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.InputStreamStep; +import com.pubnub.api.endpoints.remoteaction.ComposableRemoteAction; +import com.pubnub.api.endpoints.remoteaction.MappingRemoteAction; +import com.pubnub.api.endpoints.remoteaction.RemoteAction; +import com.pubnub.api.endpoints.remoteaction.RetryingRemoteAction; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.files.PNBaseFile; +import com.pubnub.api.models.consumer.files.PNFileUploadResult; +import com.pubnub.api.models.consumer.files.PNPublishFileMessageResult; +import com.pubnub.api.models.server.files.FileUploadRequestDetails; +import lombok.Data; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +import static com.pubnub.api.PubNubUtil.readBytes; + +@Accessors(chain = true, fluent = true) +public class SendFile implements RemoteAction { + + private final RemoteAction sendFileMultistepAction; + private final String channel; + private final String fileName; + private final byte[] content; + private final Exception byteContentReadingException; + private final ExecutorService executorService; + private final int fileMessagePublishRetryLimit; + @Setter + private Object message; + @Setter + private Object meta; + @Setter + private Integer ttl; + @Setter + private Boolean shouldStore; + @Setter + private String cipherKey; + + SendFile(Builder.SendFileRequiredParams requiredParams, + GenerateUploadUrl.Factory generateUploadUrlFactory, + ChannelStep>> publishFileMessageBuilder, + UploadFile.Factory sendFileToS3Factory, + ExecutorService executorService, + int fileMessagePublishRetryLimit) { + this.channel = requiredParams.channel(); + this.fileName = requiredParams.fileName(); + this.content = requiredParams.content(); + this.byteContentReadingException = requiredParams.byteReadingException; + this.executorService = executorService; + this.fileMessagePublishRetryLimit = fileMessagePublishRetryLimit; + this.sendFileMultistepAction = sendFileComposedActions( + generateUploadUrlFactory, + publishFileMessageBuilder, + sendFileToS3Factory); + } + + public PNFileUploadResult sync() throws PubNubException { + validate(); + return sendFileMultistepAction.sync(); + } + + public void async(@NotNull PNCallback callback) { + executorService + .execute(() -> { + try { + validate(); + sendFileMultistepAction.async(callback); + } catch (PubNubException ex) { + callback.onResponse(null, + PNStatus.builder() + .error(true) + .errorData(new PNErrorData(ex.getErrormsg(), ex)) + .build()); + } + }); + } + + private void validate() throws PubNubException { + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + + if (byteContentReadingException != null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg(byteContentReadingException.getMessage()).build(); + } + + if (content == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("Content cannot be null").build(); + } + + if (fileName == null || fileName.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("File name cannot be null nor empty").build(); + } + } + + private RemoteAction sendFileComposedActions( + GenerateUploadUrl.Factory generateUploadUrlFactory, + ChannelStep>> publishFileMessageBuilder, + UploadFile.Factory sendFileToS3Factory) { + final AtomicReference result = new AtomicReference<>(); + return ComposableRemoteAction + .firstDo(generateUploadUrlFactory.create(channel, fileName)) + .then(res -> { + result.set(res); + return sendToS3(res, sendFileToS3Factory); + }) + .checkpoint() + .then(res -> autoRetry(publishFileMessage(publishFileMessageBuilder, result), + fileMessagePublishRetryLimit)) + .then(res -> mapPublishFileMessageToFileUpload(result, res)); + } + + private PublishFileMessage publishFileMessage(ChannelStep>> publishFileMessageBuilder, + AtomicReference result) { + return publishFileMessageBuilder.channel(channel) + .fileName(result.get().getData().getName()) + .fileId(result.get().getData().getId()) + .message(message) + .meta(meta) + .ttl(ttl) + .shouldStore(shouldStore); + } + + private RemoteAction autoRetry(RemoteAction remoteAction, int maxNumberOfRetries) { + return RetryingRemoteAction.autoRetry(remoteAction, + maxNumberOfRetries, + PNOperationType.PNFileAction, + executorService); + } + + @NotNull + private RemoteAction mapPublishFileMessageToFileUpload(AtomicReference result, + PNPublishFileMessageResult res) { + return MappingRemoteAction.map(res, + pnPublishFileMessageResult -> new PNFileUploadResult(pnPublishFileMessageResult.getTimetoken(), + HttpURLConnection.HTTP_OK, + new PNBaseFile(result.get().getData().getId(), result.get().getData().getName()))); + } + + @Override + public void retry() { + sendFileMultistepAction.retry(); + } + + @Override + public void silentCancel() { + sendFileMultistepAction.silentCancel(); + } + + private RemoteAction sendToS3(FileUploadRequestDetails result, + UploadFile.Factory sendFileToS3Factory) { + return sendFileToS3Factory.create(fileName, content, cipherKey, result); + } + + public static Builder builder(PubNub pubnub, + TelemetryManager telemetry, + RetrofitManager retrofit, + TokenManager tokenManager) { + return new Builder(pubnub, telemetry, retrofit, tokenManager); + } + + public static class Builder implements ChannelStep>> { + + private final PubNub pubnub; + private final TelemetryManager telemetry; + private final RetrofitManager retrofit; + private final TokenManager tokenManager; + + Builder(PubNub pubnub, + TelemetryManager telemetry, + RetrofitManager retrofit, + TokenManager tokenManager) { + + this.pubnub = pubnub; + this.telemetry = telemetry; + this.retrofit = retrofit; + this.tokenManager = tokenManager; + } + + @Override + public FileNameStep> channel(String channel) { + return new InnerBuilder(pubnub, telemetry, retrofit, tokenManager).channel(channel); + } + + public static class InnerBuilder implements + ChannelStep>>, + FileNameStep>, + InputStreamStep { + private final PubNub pubnub; + private final RetrofitManager retrofit; + private String channelValue; + private String fileNameValue; + private final PublishFileMessage.Builder publishFileMessageBuilder; + private final UploadFile.Factory uploadFileFactory; + private final GenerateUploadUrl.Factory generateUploadUrlFactory; + + private InnerBuilder(PubNub pubnub, + TelemetryManager telemetry, + RetrofitManager retrofit, + TokenManager tokenManager) { + this.pubnub = pubnub; + this.retrofit = retrofit; + this.publishFileMessageBuilder = PublishFileMessage.builder(pubnub, telemetry, retrofit, tokenManager); + this.uploadFileFactory = new UploadFile.Factory(pubnub, retrofit); + this.generateUploadUrlFactory = new GenerateUploadUrl.Factory(pubnub, telemetry, retrofit, tokenManager); + } + + @Override + public FileNameStep> channel(String channel) { + this.channelValue = channel; + return this; + } + + @Override + public InputStreamStep fileName(String fileName) { + this.fileNameValue = fileName; + return this; + } + + @Override + public SendFile inputStream(InputStream inputStream) { + try { + return new SendFile(new SendFileRequiredParams(channelValue, + fileNameValue, + readBytes(inputStream), + null), + generateUploadUrlFactory, + publishFileMessageBuilder, + uploadFileFactory, + retrofit.getTransactionClientExecutorService(), + pubnub.getConfiguration().getFileMessagePublishRetryLimit()); + + } catch (IOException e) { + return new SendFile(new SendFileRequiredParams(channelValue, + fileNameValue, + null, + e), + generateUploadUrlFactory, + publishFileMessageBuilder, + uploadFileFactory, + retrofit.getTransactionClientExecutorService(), + pubnub.getConfiguration().getFileMessagePublishRetryLimit()); + } + } + } + + @Data + static class SendFileRequiredParams { + private final String channel; + private final String fileName; + private final byte[] content; + private final Exception byteReadingException; + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/UploadFile.java b/src/main/java/com/pubnub/api/endpoints/files/UploadFile.java new file mode 100644 index 000000000..d087d0446 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/UploadFile.java @@ -0,0 +1,321 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.remoteaction.RemoteAction; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.server.files.FileUploadRequestDetails; +import com.pubnub.api.models.server.files.FormField; +import com.pubnub.api.services.S3Service; +import com.pubnub.api.vendor.FileEncryptionUtil; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import retrofit2.Call; +import retrofit2.Response; + +import javax.net.ssl.SSLException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.List; + +import static com.pubnub.api.vendor.FileEncryptionUtil.effectiveCipherKey; + +@Slf4j +class UploadFile implements RemoteAction { + private static final MediaType APPLICATION_OCTET_STREAM = MediaType.get("application/octet-stream"); + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + private static final String FILE_PART_MULTIPART = "file"; + private final S3Service s3Service; + private final String fileName; + private final byte[] content; + private final String cipherKey; + private final FormField key; + private final List formParams; + private final String baseUrl; + private Call call; + + UploadFile(S3Service s3Service, + String fileName, + byte[] content, + String cipherKey, + FormField key, + List formParams, + String baseUrl) { + this.s3Service = s3Service; + this.fileName = fileName; + this.content = content; + this.cipherKey = cipherKey; + this.key = key; + this.formParams = formParams; + this.baseUrl = baseUrl; + } + + private static void addFormParamsWithKeyFirst(FormField keyValue, + List formParams, + MultipartBody.Builder builder) { + builder.addFormDataPart(keyValue.getKey(), keyValue.getValue()); + for (FormField it : formParams) { + if (!it.getKey().equals(keyValue.getKey())) { + builder.addFormDataPart(it.getKey(), it.getValue()); + } + } + } + + private Call prepareCall() throws PubNubException, IOException { + MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); + addFormParamsWithKeyFirst(key, formParams, builder); + MediaType mediaType = getMediaType(getContentType(formParams)); + + RequestBody requestBody; + if (cipherKey == null) { + requestBody = RequestBody.create(mediaType, content); + } else { + requestBody = RequestBody.create(mediaType, FileEncryptionUtil.encryptToBytes(cipherKey, content)); + } + + builder.addFormDataPart(FILE_PART_MULTIPART, fileName, requestBody); + return s3Service.upload(baseUrl, builder.build()); + } + + @Nullable + private String getContentType(List formFields) { + String contentType = null; + for (FormField field : formFields) { + if (field.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) { + contentType = field.getValue(); + break; + } + } + return contentType; + } + + private MediaType getMediaType(@Nullable String contentType) { + if (contentType == null) { + return APPLICATION_OCTET_STREAM; + } + + try { + return MediaType.get(contentType); + } catch (Throwable t) { + log.warn("Content-Type: " + contentType + " was not recognized by MediaType.get", t); + return APPLICATION_OCTET_STREAM; + } + } + + @Override + public Void sync() throws PubNubException { + try { + call = prepareCall(); + } catch (IOException e) { + throw PubNubException.builder() + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + + Response serverResponse; + try { + serverResponse = call.execute(); + } catch (IOException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR) + .errormsg(e.toString()) + .affectedCall(call) + .cause(e) + .build(); + } + + if (!serverResponse.isSuccessful()) { + throw createException(serverResponse); + } + return null; + } + + @Override + public void async(@NotNull PNCallback callback) { + try { + call = prepareCall(); + call.enqueue(new retrofit2.Callback() { + + @Override + public void onResponse(@NotNull Call performedCall, @NotNull Response response) { + if (!response.isSuccessful()) { + PubNubException ex = createException(response); + + PNStatusCategory pnStatusCategory = PNStatusCategory.PNUnknownCategory; + + if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED + || response.code() == HttpURLConnection.HTTP_FORBIDDEN) { + pnStatusCategory = PNStatusCategory.PNAccessDeniedCategory; + } + + if (response.code() == HttpURLConnection.HTTP_BAD_REQUEST) { + pnStatusCategory = PNStatusCategory.PNBadRequestCategory; + } + + callback.onResponse(null, + createStatusResponse(pnStatusCategory, response, ex)); + return; + } + + callback.onResponse(null, + createStatusResponse(PNStatusCategory.PNAcknowledgmentCategory, response, + null)); + } + + @Override + public void onFailure(@NotNull Call performedCall, @NotNull Throwable throwable) { + if (call.isCanceled()) { + return; + } + + PNStatusCategory pnStatusCategory; + + PubNubException.PubNubExceptionBuilder pubnubException = PubNubException.builder() + .errormsg(throwable.getMessage()) + .cause(throwable); + + try { + throw throwable; + } catch (UnknownHostException networkException) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_CONNECTION_NOT_SET); + pnStatusCategory = PNStatusCategory.PNUnexpectedDisconnectCategory; + } catch (SocketException | SSLException exception) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_CONNECT_EXCEPTION); + pnStatusCategory = PNStatusCategory.PNUnexpectedDisconnectCategory; + } catch (SocketTimeoutException socketTimeoutException) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_SOCKET_TIMEOUT); + pnStatusCategory = PNStatusCategory.PNTimeoutCategory; + } catch (Throwable throwable1) { + pubnubException.pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR); + if (performedCall.isCanceled()) { + pnStatusCategory = PNStatusCategory.PNCancelledCategory; + } else { + pnStatusCategory = PNStatusCategory.PNBadRequestCategory; + } + } + + callback.onResponse(null, createStatusResponse(pnStatusCategory, null, pubnubException.build())); + + } + }); + + } catch (IOException | PubNubException e) { + //FIXME which category shall this error belong to? + callback.onResponse(null, + createStatusResponse(PNStatusCategory.PNUnknownCategory, null, e)); + } + } + + @Override + public void retry() { + } + + @Override + public void silentCancel() { + if (!call.isCanceled()) { + call.cancel(); + } + } + + private PubNubException createException(Response response) { + try { + String responseBodyText = "N/A"; + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(response.errorBody().byteStream()); + doc.getDocumentElement().normalize(); + NodeList elements = doc.getElementsByTagName("Message"); + for (int i = 0; i < elements.getLength(); i++) { + responseBodyText = elements.item(0).getFirstChild().getNodeValue(); + } + + return PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR) + .errormsg(responseBodyText) + .affectedCall(call) + .statusCode(response.code()) + .build(); + } catch (IOException | ParserConfigurationException | SAXException | NullPointerException e) { + return PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_HTTP_ERROR) + .errormsg(e.getMessage()) + .affectedCall(call) + .statusCode(response.code()) + .cause(e) + .build(); + } + } + + private PNStatus createStatusResponse(PNStatusCategory category, Response response, Exception throwable) { + PNStatus.PNStatusBuilder pnStatus = PNStatus.builder(); + + if (response == null || throwable != null) { + pnStatus.error(true); + } + if (throwable != null) { + PNErrorData pnErrorData = new PNErrorData(throwable.getMessage(), throwable); + pnStatus.errorData(pnErrorData); + } + + if (response != null) { + pnStatus.statusCode(response.code()); + pnStatus.tlsEnabled(response.raw().request().url().isHttps()); + pnStatus.origin(response.raw().request().url().host()); + pnStatus.clientRequest(response.raw().request()); + } + + pnStatus.operation(getOperationType()); + pnStatus.category(category); + + return pnStatus.build(); + } + + private PNOperationType getOperationType() { + return PNOperationType.PNFileAction; + } + + static class Factory { + private final PubNub pubNub; + private final RetrofitManager retrofitManager; + + Factory(PubNub pubNub, RetrofitManager retrofitManager) { + this.pubNub = pubNub; + this.retrofitManager = retrofitManager; + } + + RemoteAction create(String fileName, + byte[] content, + String cipherKey, + FileUploadRequestDetails fileUploadRequestDetails) { + String effectiveCipherKey = effectiveCipherKey(pubNub, cipherKey); + + return new UploadFile(retrofitManager.getS3Service(), + fileName, + content, + effectiveCipherKey, + fileUploadRequestDetails.getKeyFormField(), fileUploadRequestDetails.getFormFields(), + fileUploadRequestDetails.getUrl()); + } + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/ChannelFileNameFileIdBuilder.java b/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/ChannelFileNameFileIdBuilder.java new file mode 100644 index 000000000..7ddc7442f --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/ChannelFileNameFileIdBuilder.java @@ -0,0 +1,55 @@ +package com.pubnub.api.endpoints.files.requiredparambuilder; + +import com.pubnub.api.endpoints.BuilderSteps.ChannelStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileIdStep; +import com.pubnub.api.endpoints.files.requiredparambuilder.FilesBuilderSteps.FileNameStep; +import com.pubnub.api.endpoints.remoteaction.PNFunction3; + +public abstract class ChannelFileNameFileIdBuilder implements + ChannelStep>> { + private final ChannelStep>> builder; + + public ChannelFileNameFileIdBuilder(ChannelStep>> builder) { + + this.builder = builder; + } + + public static ChannelStep>> create(PNFunction3 lastStep) { + return new InnerBuilder<>(lastStep); + } + + @Override + public FileNameStep> channel(String channel) { + return builder.channel(channel); + } + + public static class InnerBuilder implements + ChannelStep>>, + FileNameStep>, + FileIdStep { + private final PNFunction3 lastStep; + private String channelValue; + private String fileNameValue; + + private InnerBuilder(PNFunction3 lastStep) { + this.lastStep = lastStep; + } + + @Override + public FileNameStep> channel(String channel) { + this.channelValue = channel; + return this; + } + + @Override + public FileIdStep fileName(String fileName) { + this.fileNameValue = fileName; + return this; + } + + @Override + public T fileId(String fileId) { + return lastStep.invoke(channelValue, fileNameValue, fileId); + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/FilesBuilderSteps.java b/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/FilesBuilderSteps.java new file mode 100644 index 000000000..f8fcb8cbd --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/files/requiredparambuilder/FilesBuilderSteps.java @@ -0,0 +1,22 @@ +package com.pubnub.api.endpoints.files.requiredparambuilder; + +import com.pubnub.api.endpoints.BuilderSteps; + +import java.io.IOException; +import java.io.InputStream; + +public interface FilesBuilderSteps extends BuilderSteps { + + interface FileNameStep { + T fileName(String fileName); + } + + interface InputStreamStep { + T inputStream(InputStream inputStream) throws IOException; + } + + interface FileIdStep { + T fileId(String fileId); + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/message_actions/AddMessageAction.java b/src/main/java/com/pubnub/api/endpoints/message_actions/AddMessageAction.java new file mode 100644 index 000000000..e461f0088 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/message_actions/AddMessageAction.java @@ -0,0 +1,109 @@ +package com.pubnub.api.endpoints.message_actions; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.message_actions.PNAddMessageActionResult; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class AddMessageAction extends Endpoint, PNAddMessageActionResult> { + + @Setter + private String channel; + + @Setter + private PNMessageAction messageAction; + + public AddMessageAction(PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (messageAction == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_MISSING).build(); + } + if (messageAction.getMessageTimetoken() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_TIMETOKEN_MISSING).build(); + } + if (messageAction.getType() == null || messageAction.getType().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_TYPE_MISSING) + .build(); + } + if (messageAction.getValue() == null || messageAction.getValue().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_VALUE_MISSING) + .build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) { + + params.putAll(encodeParams(params)); + + JsonObject body = new JsonObject(); + body.addProperty("type", messageAction.getType()); + body.addProperty("value", messageAction.getValue()); + + return this.getRetrofit() + .getMessageActionService() + .addMessageAction(this.getPubnub().getConfiguration().getSubscribeKey(), channel, + Long.toString(messageAction.getMessageTimetoken()).toLowerCase(), body, params); + } + + @Override + protected PNAddMessageActionResult createResponse(Response> input) + throws PubNubException { + PNAddMessageActionResult.PNAddMessageActionResultBuilder builder = PNAddMessageActionResult.builder(); + if (input.body() != null) { + builder.pnMessageAction(input.body().getData()); + } + return builder.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNAddMessageAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/endpoints/message_actions/GetMessageActions.java b/src/main/java/com/pubnub/api/endpoints/message_actions/GetMessageActions.java new file mode 100644 index 000000000..0b834e2b7 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/message_actions/GetMessageActions.java @@ -0,0 +1,103 @@ +package com.pubnub.api.endpoints.message_actions; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class GetMessageActions extends Endpoint { + + @Setter + private String channel; + + @Setter + private Long start; + + @Setter + private Long end; + + @Setter + private Integer limit; + + public GetMessageActions(PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) { + + if (start != null) { + params.put("start", Long.toString(start).toLowerCase()); + } + + if (end != null) { + params.put("end", Long.toString(end).toLowerCase()); + } + + if (limit != null) { + params.put("limit", String.valueOf(limit)); + } + + params.putAll(encodeParams(params)); + + return this.getRetrofit() + .getMessageActionService() + .getMessageActions(this.getPubnub().getConfiguration().getSubscribeKey(), channel, params); + } + + @Override + protected PNGetMessageActionsResult createResponse(Response input) throws + PubNubException { + return input.body(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetMessageActions; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.java b/src/main/java/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.java new file mode 100644 index 000000000..d71e06fcf --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/message_actions/RemoveMessageAction.java @@ -0,0 +1,96 @@ +package com.pubnub.api.endpoints.message_actions; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.message_actions.PNRemoveMessageActionResult; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class RemoveMessageAction extends Endpoint { + + @Setter + private String channel; + + @Setter + private Long messageTimetoken; + + @Setter + private Long actionTimetoken; + + public RemoveMessageAction(PubNub pubnubInstance, + TelemetryManager telemetry, + RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (messageTimetoken == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_TIMETOKEN_MISSING).build(); + } + if (actionTimetoken == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_ACTION_TIMETOKEN_MISSING) + .build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) throws PubNubException { + + params.putAll(encodeParams(params)); + + return this.getRetrofit() + .getMessageActionService() + .deleteMessageAction(this.getPubnub().getConfiguration().getSubscribeKey(), channel, + Long.toString(messageTimetoken).toLowerCase(), Long.toString(actionTimetoken).toLowerCase(), + params); + } + + @Override + protected PNRemoveMessageActionResult createResponse(Response input) throws PubNubException { + return new PNRemoveMessageActionResult(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNDeleteMessageAction; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/ChannelEnpoint.java b/src/main/java/com/pubnub/api/endpoints/objects_api/ChannelEnpoint.java new file mode 100644 index 000000000..b75636b7f --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/ChannelEnpoint.java @@ -0,0 +1,31 @@ +package com.pubnub.api.endpoints.objects_api; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; + +public abstract class ChannelEnpoint extends ObjectApiEndpoint { + protected String channel; + + protected ChannelEnpoint(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + this.channel = channel; + } + + @Override + protected void validateParams() throws PubNubException { + super.validateParams(); + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + } +} + diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/CompositeParameterEnricher.java b/src/main/java/com/pubnub/api/endpoints/objects_api/CompositeParameterEnricher.java new file mode 100644 index 000000000..2276fe462 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/CompositeParameterEnricher.java @@ -0,0 +1,70 @@ +package com.pubnub.api.endpoints.objects_api; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.utils.*; +import lombok.Getter; + +import java.util.*; + +public class CompositeParameterEnricher implements ParameterEnricher { + @Getter + private final Include include; + @Getter private final Sorter sorter; + @Getter private final Pager pager; + @Getter private final Filter filter; + @Getter private final TotalCounter totalCounter; + @Getter private final Limiter limiter; + + public static CompositeParameterEnricher createDefault() { + final Include include = new Include(); + final Sorter sorter = new Sorter(); + final Pager pager = new Pager(); + final Filter filter = new Filter(); + final Limiter limiter = new Limiter(); + final TotalCounter totalCounter = new TotalCounter(); + + return new CompositeParameterEnricher(include, sorter, pager, filter, totalCounter, limiter); + } + + public CompositeParameterEnricher(final Include include, + final Sorter sorter, + final Pager pager, + final Filter filter, + final TotalCounter totalCounter, + final Limiter limiter) { + this.include = include; + this.sorter = sorter; + this.pager = pager; + this.filter = filter; + this.totalCounter = totalCounter; + this.limiter = limiter; + } + + + + @Override + public Map enrichParameters(final Map baseParams) { + Map enrichedMap = new HashMap<>(baseParams); + + final List parameterEnrichers = new ArrayList<>(); + for (final ParameterEnricher enricher : Arrays.asList(include, sorter, pager, filter, totalCounter, limiter)) { + if (enricher != null) { + parameterEnrichers.add(enricher); + } + } + + for (final ParameterEnricher parameterEnricher : parameterEnrichers) { + enrichedMap = parameterEnricher.enrichParameters(enrichedMap); + } + + return enrichedMap; + } + + @Override + public void validateParameters() throws PubNubException { + for (final ParameterEnricher parameterEnricher + : Arrays.asList(include, sorter, pager, filter, totalCounter, limiter)) { + parameterEnricher.validateParameters(); + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/ObjectApiEndpoint.java b/src/main/java/com/pubnub/api/endpoints/objects_api/ObjectApiEndpoint.java new file mode 100644 index 000000000..bfb78e211 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/ObjectApiEndpoint.java @@ -0,0 +1,63 @@ +package com.pubnub.api.endpoints.objects_api; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import lombok.Getter; +import retrofit2.Call; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static lombok.AccessLevel.PROTECTED; + +public abstract class ObjectApiEndpoint extends Endpoint { + + @Getter(PROTECTED) + private final CompositeParameterEnricher compositeParameterEnricher; + + protected ObjectApiEndpoint(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, tokenManager); + this.compositeParameterEnricher = compositeParameterEnricher; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + compositeParameterEnricher.validateParameters(); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + return executeCommand(encodeParams(compositeParameterEnricher.enrichParameters(new HashMap<>(baseParams)))); + } + + protected abstract Call executeCommand(Map effectiveParams) throws PubNubException; +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/UUIDEndpoint.java b/src/main/java/com/pubnub/api/endpoints/objects_api/UUIDEndpoint.java new file mode 100644 index 000000000..4c028ca5f --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/UUIDEndpoint.java @@ -0,0 +1,40 @@ +package com.pubnub.api.endpoints.objects_api; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; + +public abstract class UUIDEndpoint extends ObjectApiEndpoint { + private String uuid; + + protected UUIDEndpoint( + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected void validateParams() throws PubNubException { + super.validateParams(); + final String effectiveUuid = effectiveUuid(); + if (effectiveUuid == null || effectiveUuid.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_UUID_MISSING).build(); + } + } + + public SELF uuid(final String uuid) { + this.uuid = uuid; + return (SELF) this; + } + + protected String effectiveUuid() { + return (uuid != null) ? uuid : getPubnub().getConfiguration().getUuid(); + } +} + diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetAllChannelsMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetAllChannelsMetadata.java new file mode 100644 index 000000000..ed6418cbc --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetAllChannelsMetadata.java @@ -0,0 +1,82 @@ +package com.pubnub.api.endpoints.objects_api.channel; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.ObjectApiEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.channel.PNGetAllChannelsMetadataResult; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetAllChannelsMetadata + extends ObjectApiEndpoint, PNGetAllChannelsMetadataResult> + implements CustomIncludeAware, ListCapabilitiesAware { + GetAllChannelsMetadata(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static GetAllChannelsMetadata create(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new GetAllChannelsMetadataCommand(pubnubInstance, telemetry, retrofitInstance, + compositeParameterEnricher, tokenManager); + } +} + +final class GetAllChannelsMetadataCommand extends GetAllChannelsMetadata implements + HavingCustomInclude, + HavingListCapabilites { + GetAllChannelsMetadataCommand(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getChannelMetadataService() + .getChannelMetadata(getPubnub().getConfiguration().getSubscribeKey(), effectiveParams); + } + + @Override + protected PNGetAllChannelsMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNGetAllChannelsMetadataResult(input.body()); + } else { + return new PNGetAllChannelsMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetAllChannelsMetadataOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetChannelMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetChannelMetadata.java new file mode 100644 index 000000000..c953128b4 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/GetChannelMetadata.java @@ -0,0 +1,94 @@ +package com.pubnub.api.endpoints.objects_api.channel; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.channel.PNGetChannelMetadataResult; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetChannelMetadata + extends ChannelEnpoint, PNGetChannelMetadataResult> + implements CustomIncludeAware { + GetChannelMetadata(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public GetChannelMetadata channel(final String channel) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new GetChannelMetadataCommand(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + } +} + +final class GetChannelMetadataCommand extends GetChannelMetadata implements HavingCustomInclude { + GetChannelMetadataCommand(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getChannelMetadataService() + .getChannelMetadata(getPubnub().getConfiguration().getSubscribeKey(), channel, effectiveParams); + } + + @Override + protected PNGetChannelMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNGetChannelMetadataResult(input.body()); + } else { + return new PNGetChannelMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetChannelMetadataOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/channel/RemoveChannelMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/RemoveChannelMetadata.java new file mode 100644 index 000000000..e6a26bf19 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/RemoveChannelMetadata.java @@ -0,0 +1,70 @@ +package com.pubnub.api.endpoints.objects_api.channel; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.channel.PNRemoveChannelMetadataResult; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public class RemoveChannelMetadata extends ChannelEnpoint, PNRemoveChannelMetadataResult> { + RemoveChannelMetadata(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, CompositeParameterEnricher.createDefault(), + tokenManager); + } + + @Override + protected Call> executeCommand(Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getChannelMetadataService() + .deleteChannelMetadata(getPubnub().getConfiguration().getSubscribeKey(), channel, effectiveParams); + } + + @Override + protected PNRemoveChannelMetadataResult createResponse(Response> input) + throws PubNubException { + return new PNRemoveChannelMetadataResult(input.body()); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveChannelMetadataOperation; + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public RemoveChannelMetadata channel(final String channel) { + return new RemoveChannelMetadata(channel, pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadata.java new file mode 100644 index 000000000..5b91a80aa --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadata.java @@ -0,0 +1,131 @@ +package com.pubnub.api.endpoints.objects_api.channel; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.channel.PNSetChannelMetadataResult; +import com.pubnub.api.models.server.objects_api.SetChannelMetadataPayload; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.HashMap; +import java.util.Map; + +public abstract class SetChannelMetadata + extends ChannelEnpoint, PNSetChannelMetadataResult> + implements CustomIncludeAware { + SetChannelMetadata(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public abstract SetChannelMetadata description(String description); + + public abstract SetChannelMetadata name(String name); + + public abstract SetChannelMetadata custom(Map custom); + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new Builder(pubnubInstance, telemetry, retrofitInstance, + compositeParameterEnricher, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final CompositeParameterEnricher compositeParameterEnricher; + private final TokenManager tokenManager; + + @Override + public SetChannelMetadata channel(final String channel) { + return new SetChannelMetadataCommand(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } + } +} + +final class SetChannelMetadataCommand extends SetChannelMetadata implements HavingCustomInclude { + private String name; + private String description; + private Object custom; + + SetChannelMetadataCommand(final String channel, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + final SetChannelMetadataPayload setChannelMetadataPayload = new SetChannelMetadataPayload(name, description, + custom); + return getRetrofit() + .getChannelMetadataService() + .setChannelsMetadata(getPubnub().getConfiguration().getSubscribeKey(), channel, + setChannelMetadataPayload, effectiveParams); + } + + @Override + protected PNSetChannelMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNSetChannelMetadataResult(input.body()); + } else { + return new PNSetChannelMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSetChannelMetadataOperation; + } + + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } + + @Override + public SetChannelMetadata description(final String description) { + this.description = description; + return this; + } + + @Override + public SetChannelMetadata name(String name) { + this.name = name; + return this; + } + + @Override + public SetChannelMetadata custom(final Map custom) { + this.custom = new HashMap<>(custom); + return this; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/members/GetChannelMembers.java b/src/main/java/com/pubnub/api/endpoints/objects_api/members/GetChannelMembers.java new file mode 100644 index 000000000..44c9f82ac --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/members/GetChannelMembers.java @@ -0,0 +1,102 @@ +package com.pubnub.api.endpoints.objects_api.members; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingUUIDInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.UUIDIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.member.PNGetChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetChannelMembers extends ChannelEnpoint, PNGetChannelMembersResult> + implements CustomIncludeAware, UUIDIncludeAware, ListCapabilitiesAware { + GetChannelMembers(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new Builder(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final CompositeParameterEnricher compositeParameterEnricher; + private final TokenManager tokenManager; + + @Override + public GetChannelMembers channel(final String channel) { + return new GetChannelMembersCommand(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + } +} + +final class GetChannelMembersCommand extends GetChannelMembers implements + HavingCustomInclude, + HavingUUIDInclude, + HavingListCapabilites { + + GetChannelMembersCommand(final String channel, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getChannelMetadataService() + .getMembers(getPubnub().getConfiguration().getSubscribeKey(), channel, effectiveParams); + } + + @Override + protected PNGetChannelMembersResult createResponse(Response> input) throws PubNubException { + if (input.body() != null) { + return new PNGetChannelMembersResult(input.body()); + } else { + return new PNGetChannelMembersResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetChannelMembersOperation; + } + + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/members/ManageChannelMembers.java b/src/main/java/com/pubnub/api/endpoints/objects_api/members/ManageChannelMembers.java new file mode 100644 index 000000000..6646b1638 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/members/ManageChannelMembers.java @@ -0,0 +1,151 @@ +package com.pubnub.api.endpoints.objects_api.members; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingUUIDInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.UUIDIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.member.PNManageChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.server.objects_api.PatchMemberPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class ManageChannelMembers extends ChannelEnpoint, PNManageChannelMembersResult> + implements CustomIncludeAware, UUIDIncludeAware, ListCapabilitiesAware { + ManageChannelMembers(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements ObjectsBuilderSteps.ChannelStep> { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public ObjectsBuilderSteps.RemoveOrSetStep channel(final String channel) { + return new ObjectsBuilderSteps.RemoveOrSetStep() { + @Override + public RemoveStep set(final Collection uuidsToSet) { + return new RemoveStep() { + @Override + public ManageChannelMembers remove(final Collection uuidsToRemove) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher + .createDefault(); + return new ManageChannelMembersCommand(channel, + uuidsToSet, + uuidsToRemove, + pubnubInstance, + telemetry, + retrofitInstance, + compositeParameterEnricher, + tokenManager); + } + }; + } + + @Override + public SetStep remove(final Collection uuidsToRemove) { + return new SetStep() { + @Override + public ManageChannelMembers set(final Collection uuidsToSet) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher + .createDefault(); + return new ManageChannelMembersCommand(channel, + uuidsToSet, + uuidsToRemove, + pubnubInstance, + telemetry, + retrofitInstance, + compositeParameterEnricher, + tokenManager); + } + }; + } + }; + } + } +} + +final class ManageChannelMembersCommand extends ManageChannelMembers implements + HavingCustomInclude, + HavingUUIDInclude, + HavingListCapabilites { + private final Collection uuidsToSet; + private final Collection uuidsToRemove; + + ManageChannelMembersCommand(final String channel, + final Collection uuidsToSet, + final Collection uuidsToRemove, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + this.uuidsToSet = uuidsToSet; + this.uuidsToRemove = uuidsToRemove; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) throws PubNubException { + final PatchMemberPayload patchMemberBody = new PatchMemberPayload( + (uuidsToSet != null) ? uuidsToSet : Collections.emptyList(), + (uuidsToRemove != null) ? uuidsToRemove : Collections.emptyList()); + + return getRetrofit() + .getChannelMetadataService() + .patchMembers(getPubnub().getConfiguration().getSubscribeKey(), channel, patchMemberBody, + effectiveParams); + } + + @Override + protected PNManageChannelMembersResult createResponse(final Response> input) throws PubNubException { + if (input.body() != null) { + return new PNManageChannelMembersResult(input.body()); + } else { + return new PNManageChannelMembersResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNManageChannelMembersOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/members/RemoveChannelMembers.java b/src/main/java/com/pubnub/api/endpoints/objects_api/members/RemoveChannelMembers.java new file mode 100644 index 000000000..bb35306a3 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/members/RemoveChannelMembers.java @@ -0,0 +1,119 @@ +package com.pubnub.api.endpoints.objects_api.members; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingUUIDInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.UUIDIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.consumer.objects_api.member.PNRemoveChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.server.objects_api.PatchMemberPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class RemoveChannelMembers extends ChannelEnpoint, PNRemoveChannelMembersResult> + implements CustomIncludeAware, UUIDIncludeAware, ListCapabilitiesAware { + + RemoveChannelMembers(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep> { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public ObjectsBuilderSteps.UUIDsStep channel(final String channel) { + return new ObjectsBuilderSteps.UUIDsStep() { + @Override + public RemoveChannelMembers uuids(@NotNull final Collection uuids) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new RemoveChannelMembersCommand(channel, uuids, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + }; + } + } +} + +final class RemoveChannelMembersCommand extends RemoveChannelMembers implements + HavingCustomInclude, + HavingUUIDInclude, + HavingListCapabilites { + private final Collection uuids; + + RemoveChannelMembersCommand(final String channel, + final Collection uuids, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + TokenManager tokenManager) { + super(channel, pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + this.uuids = uuids; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + final PatchMemberPayload patchMemberBody = new PatchMemberPayload(Collections.emptyList(), uuids); + + return getRetrofit() + .getChannelMetadataService() + .patchMembers(getPubnub().getConfiguration().getSubscribeKey(), channel, patchMemberBody, + effectiveParams); + } + + @Override + protected PNRemoveChannelMembersResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNRemoveChannelMembersResult(input.body()); + } else { + return new PNRemoveChannelMembersResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveChannelMembersOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembers.java b/src/main/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembers.java new file mode 100644 index 000000000..f6ecfe054 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembers.java @@ -0,0 +1,118 @@ +package com.pubnub.api.endpoints.objects_api.members; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.endpoints.objects_api.ChannelEnpoint; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingUUIDInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.UUIDIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.member.PNSetChannelMembersResult; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.server.objects_api.PatchMemberPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class SetChannelMembers extends ChannelEnpoint, PNSetChannelMembersResult> + implements CustomIncludeAware, UUIDIncludeAware, ListCapabilitiesAware { + + SetChannelMembers(final String channel, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements BuilderSteps.ChannelStep> { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public ObjectsBuilderSteps.UUIDsStep channel(final String channel) { + return new ObjectsBuilderSteps.UUIDsStep() { + @Override + public SetChannelMembers uuids(@NotNull final Collection uuids) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new SetChannelMembersCommand(channel, uuids, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } + }; + } + } +} + +final class SetChannelMembersCommand extends SetChannelMembers + implements HavingCustomInclude, HavingUUIDInclude, HavingListCapabilites { + private final Collection uuids; + + SetChannelMembersCommand(final String channel, + final Collection uuids, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(channel, pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + this.uuids = uuids; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + final PatchMemberPayload patchMemberBody = new PatchMemberPayload(uuids, Collections.emptyList()); + + return getRetrofit() + .getChannelMetadataService() + .patchMembers(getPubnub().getConfiguration().getSubscribeKey(), channel, patchMemberBody, + effectiveParams); + } + + @Override + protected PNSetChannelMembersResult createResponse(final Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNSetChannelMembersResult(input.body()); + } else { + return new PNSetChannelMembersResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSetChannelMembersOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/GetMemberships.java b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/GetMemberships.java new file mode 100644 index 000000000..39de9ad99 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/GetMemberships.java @@ -0,0 +1,88 @@ +package com.pubnub.api.endpoints.objects_api.memberships; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.ChannelIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingChannelInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.membership.PNGetMembershipsResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetMemberships extends UUIDEndpoint, PNGetMembershipsResult> + implements CustomIncludeAware, ChannelIncludeAware, + ListCapabilitiesAware { + + public GetMemberships(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static GetMemberships create(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new GetMembershipsCommand(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } +} + +final class GetMembershipsCommand extends GetMemberships + implements HavingCustomInclude, HavingChannelInclude, + HavingListCapabilites { + GetMembershipsCommand(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getUuidMetadataService() + .getMemberships(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), effectiveParams); + + } + + @Override + protected PNGetMembershipsResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNGetMembershipsResult(input.body()); + } else { + return new PNGetMembershipsResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetMembershipsOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} + + diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/ManageMemberships.java b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/ManageMemberships.java new file mode 100644 index 000000000..46db9403b --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/ManageMemberships.java @@ -0,0 +1,145 @@ +package com.pubnub.api.endpoints.objects_api.memberships; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.ChannelIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingChannelInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNManageMembershipResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.server.objects_api.PatchMembershipPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class ManageMemberships extends UUIDEndpoint, PNManageMembershipResult> + implements CustomIncludeAware, ChannelIncludeAware, + ListCapabilitiesAware { + + ManageMemberships(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements ObjectsBuilderSteps.RemoveOrSetStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public RemoveStep set(final Collection channelsToSet) { + return new RemoveStep() { + @Override + public ManageMemberships remove(final Collection channelsToRemove) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher + .createDefault(); + return new ManageMembershipsCommand(channelsToSet, + channelsToRemove, + pubnubInstance, + telemetry, + retrofitInstance, + compositeParameterEnricher, + tokenManager); + } + }; + } + + @Override + public SetStep remove(final Collection channelsToRemove) { + return new SetStep() { + @Override + public ManageMemberships set(final Collection channelsToSet) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher + .createDefault(); + return new ManageMembershipsCommand(channelsToSet, + channelsToRemove, + pubnubInstance, + telemetry, + retrofitInstance, + compositeParameterEnricher, + tokenManager); + } + }; + } + } +} + +final class ManageMembershipsCommand extends ManageMemberships implements + HavingCustomInclude, + HavingChannelInclude, + HavingListCapabilites { + private final Collection channelsToSet; + private final Collection channelsToRemove; + + ManageMembershipsCommand(final Collection channelsToSet, + final Collection channelsToRemove, + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + this.channelsToSet = channelsToSet; + this.channelsToRemove = channelsToRemove; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) throws PubNubException { + final PatchMembershipPayload patchMembershipBody = new PatchMembershipPayload( + (channelsToSet != null) ? channelsToSet : Collections.emptyList(), + (channelsToRemove != null) ? channelsToRemove : Collections.emptyList()); + + return getRetrofit() + .getUuidMetadataService() + .patchMembership(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), patchMembershipBody, + effectiveParams); + } + + @Override + protected PNManageMembershipResult createResponse(final Response> input) throws PubNubException { + if (input.body() != null) { + return new PNManageMembershipResult(input.body()); + } else { + return new PNManageMembershipResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNManageMembershipsOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} + diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/RemoveMemberships.java b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/RemoveMemberships.java new file mode 100644 index 000000000..714d40832 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/RemoveMemberships.java @@ -0,0 +1,113 @@ +package com.pubnub.api.endpoints.objects_api.memberships; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.ChannelIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingChannelInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNRemoveMembershipResult; +import com.pubnub.api.models.server.objects_api.PatchMembershipPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class RemoveMemberships extends UUIDEndpoint, PNRemoveMembershipResult> + implements CustomIncludeAware, ChannelIncludeAware, + ListCapabilitiesAware { + + RemoveMemberships(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements ObjectsBuilderSteps.ChannelMembershipsStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public RemoveMemberships channelMemberships(@NotNull final Collection channelMemberships) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new RemoveMembershipsCommand(channelMemberships, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } + } +} + +final class RemoveMembershipsCommand extends RemoveMemberships + implements HavingCustomInclude, HavingChannelInclude, + HavingListCapabilites { + private final Collection channels; + + RemoveMembershipsCommand(final Collection channels, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + this.channels = channels; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + final PatchMembershipPayload patchMembershipBody = new PatchMembershipPayload(Collections.emptyList(), + channels); + + return getRetrofit() + .getUuidMetadataService() + .patchMembership(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), patchMembershipBody, + effectiveParams); + } + + @Override + protected PNRemoveMembershipResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNRemoveMembershipResult(input.body()); + } else { + return new PNRemoveMembershipResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveMembershipsOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/SetMemberships.java b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/SetMemberships.java new file mode 100644 index 000000000..ac73c6fa1 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/memberships/SetMemberships.java @@ -0,0 +1,111 @@ +package com.pubnub.api.endpoints.objects_api.memberships; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.ChannelIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingChannelInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.endpoints.objects_api.utils.ObjectsBuilderSteps; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.membership.PNSetMembershipResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.server.objects_api.PatchMembershipPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class SetMemberships extends UUIDEndpoint, PNSetMembershipResult> + implements CustomIncludeAware, ChannelIncludeAware, + ListCapabilitiesAware { + SetMemberships(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static Builder builder(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + return new Builder(pubnubInstance, telemetry, retrofitInstance, tokenManager); + } + + @AllArgsConstructor + public static class Builder implements ObjectsBuilderSteps.ChannelMembershipsStep { + private final PubNub pubnubInstance; + private final TelemetryManager telemetry; + private final RetrofitManager retrofitInstance; + private final TokenManager tokenManager; + + @Override + public SetMemberships channelMemberships(@NotNull final Collection channelMemberships) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new SetMembershipsCommand(channelMemberships, pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } + } +} + +final class SetMembershipsCommand extends SetMemberships implements HavingCustomInclude, + HavingChannelInclude, + HavingListCapabilites { + private final Collection channelMemberships; + + SetMembershipsCommand(final Collection channelMemberships, + final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + this.channelMemberships = channelMemberships; + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + final PatchMembershipPayload patchMembershipBody = new PatchMembershipPayload(channelMemberships, + Collections.emptyList()); + return getRetrofit() + .getUuidMetadataService() + .patchMembership(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), patchMembershipBody, + effectiveParams); + } + + @Override + protected PNSetMembershipResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNSetMembershipResult(input.body()); + } else { + return new PNSetMembershipResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSetMembershipsOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Filter.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Filter.java new file mode 100644 index 000000000..c31df8138 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Filter.java @@ -0,0 +1,38 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.endpoints.Endpoint; + +import java.util.HashMap; +import java.util.Map; + +public class Filter implements ParameterEnricher { + static final String FILTER_PARAM_NAME = "filter"; + + public interface FilterAware> { + T filter(String filter); + } + + public interface HavingFilter> extends FilterAware, HavingCompositeParameterEnricher { + @Override + default T filter(String filter) { + getCompositeParameterEnricher().getFilter().setFilter(filter); + return (T) this; + } + } + + private String filter; + + public void setFilter(String filter) { + this.filter = filter; + } + + @Override + public Map enrichParameters(Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (filter != null) { + enrichedMap.put(FILTER_PARAM_NAME, PubNubUtil.urlEncode(filter)); + } + return enrichedMap; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/HavingCompositeParameterEnricher.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/HavingCompositeParameterEnricher.java new file mode 100644 index 000000000..5242cbda8 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/HavingCompositeParameterEnricher.java @@ -0,0 +1,7 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; + +public interface HavingCompositeParameterEnricher { + CompositeParameterEnricher getCompositeParameterEnricher(); +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Include.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Include.java new file mode 100644 index 000000000..2c7104193 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Include.java @@ -0,0 +1,112 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.Endpoint; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class Include implements ParameterEnricher { + static final String INCLUDE_PARAM_NAME = "include"; + static final String INCLUDE_CUSTOM_PARAM_VALUE = "custom"; + static final String INCLUDE_CHANNEL_PARAM_VALUE = "channel"; + static final String INCLUDE_CHANNEL_CUSTOM_PARAM_VALUE = "channel.custom"; + static final String INCLUDE_UUID_PARAM_VALUE = "uuid"; + static final String INCLUDE_UUID_CUSTOM_PARAM_VALUE = "uuid.custom"; + + public interface CustomIncludeAware> { + T includeCustom(boolean includeCustom); + } + + public interface HavingCustomInclude> + extends CustomIncludeAware, HavingCompositeParameterEnricher { + @Override + default T includeCustom(boolean includeCustom) { + if (includeCustom) { + getCompositeParameterEnricher().getInclude().addInclusionFlag(INCLUDE_CUSTOM_PARAM_VALUE); + } + return (T) this; + } + } + + public enum PNChannelDetailsLevel { + CHANNEL(INCLUDE_CHANNEL_PARAM_VALUE), + CHANNEL_WITH_CUSTOM(INCLUDE_CHANNEL_CUSTOM_PARAM_VALUE); + + private final String paramValue; + + PNChannelDetailsLevel(final String paramValue) { + this.paramValue = paramValue; + } + } + + public interface ChannelIncludeAware> { + T includeChannel(PNChannelDetailsLevel channelDetailsLevel); + } + + public interface HavingChannelInclude> + extends ChannelIncludeAware, HavingCompositeParameterEnricher { + + @Override + default T includeChannel(final PNChannelDetailsLevel channelDetailsLevel) { + getCompositeParameterEnricher().getInclude().addInclusionFlag(channelDetailsLevel.paramValue); + return (T) this; + } + } + + public enum PNUUIDDetailsLevel { + UUID(INCLUDE_UUID_PARAM_VALUE), + UUID_WITH_CUSTOM(INCLUDE_UUID_CUSTOM_PARAM_VALUE); + + private final String paramValue; + + PNUUIDDetailsLevel(final String paramValue) { + this.paramValue = paramValue; + } + } + + public interface UUIDIncludeAware> { + T includeUUID(PNUUIDDetailsLevel uuidDetailsLevel); + } + + public interface HavingUUIDInclude> + extends UUIDIncludeAware, HavingCompositeParameterEnricher { + + @Override + default T includeUUID(PNUUIDDetailsLevel uuidDetailsLevel) { + getCompositeParameterEnricher().getInclude().addInclusionFlag(uuidDetailsLevel.paramValue); + return (T) this; + } + } + + private final List inclusionFlags = new ArrayList<>(); + + public void addInclusionFlag(final String inclusionFlag) { + inclusionFlags.add(inclusionFlag); + } + + @Override + public Map enrichParameters(Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (!inclusionFlags.isEmpty()) { + enrichedMap.put(INCLUDE_PARAM_NAME, join(inclusionFlags)); + } + return enrichedMap; + } + + private String join(Collection values) { + final StringBuilder builder = new StringBuilder(); + Iterator flagsIterator = values.iterator(); + + while (flagsIterator.hasNext()) { + builder.append(flagsIterator.next()); + if (flagsIterator.hasNext()) { + builder.append(","); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Limiter.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Limiter.java new file mode 100644 index 000000000..21d7aff4c --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Limiter.java @@ -0,0 +1,37 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.Endpoint; + +import java.util.HashMap; +import java.util.Map; + +public class Limiter implements ParameterEnricher { + static final String LIMIT_PARAM_NAME = "limit"; + + public interface LimitAware> { + T limit(int limit); + } + + public interface HavingLimiter> extends LimitAware, HavingCompositeParameterEnricher { + @Override + default T limit(int limit) { + getCompositeParameterEnricher().getLimiter().setLimit(limit); + return (T) this; + } + } + + private Integer limit; + + public void setLimit(int limit) { + this.limit = limit; + } + + @Override + public Map enrichParameters(Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (limit != null) { + enrichedMap.put(LIMIT_PARAM_NAME, limit.toString()); + } + return enrichedMap; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ListCapabilities.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ListCapabilities.java new file mode 100644 index 000000000..b3c9e6a36 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ListCapabilities.java @@ -0,0 +1,24 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.endpoints.objects_api.utils.Filter.FilterAware; +import com.pubnub.api.endpoints.objects_api.utils.Filter.HavingFilter; +import com.pubnub.api.endpoints.objects_api.utils.Limiter.HavingLimiter; +import com.pubnub.api.endpoints.objects_api.utils.Limiter.LimitAware; +import com.pubnub.api.endpoints.objects_api.utils.Pager.HavingPager; +import com.pubnub.api.endpoints.objects_api.utils.Pager.PagingAware; +import com.pubnub.api.endpoints.objects_api.utils.Sorter.HavingSorter; +import com.pubnub.api.endpoints.objects_api.utils.Sorter.SortingAware; +import com.pubnub.api.endpoints.objects_api.utils.TotalCounter.HavingTotalCounter; +import com.pubnub.api.endpoints.objects_api.utils.TotalCounter.TotalCountAware; + +public interface ListCapabilities { + interface ListCapabilitiesAware> + extends LimitAware, TotalCountAware, SortingAware, PagingAware, FilterAware { + } + + interface HavingListCapabilites> extends HavingLimiter, + HavingTotalCounter, HavingSorter, HavingPager, HavingFilter { + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsBuilderSteps.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsBuilderSteps.java new file mode 100644 index 000000000..041723b8a --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsBuilderSteps.java @@ -0,0 +1,31 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.BuilderSteps; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +public interface ObjectsBuilderSteps extends BuilderSteps { + interface ChannelMembershipsStep { + T channelMemberships(@NotNull Collection channelMemberships); + } + + interface UUIDsStep { + T uuids(@NotNull Collection uuids); + } + + interface RemoveOrSetStep { + RemoveStep set(Collection entitiesToSet); + SetStep remove(Collection entitiesToRemove); + + interface RemoveStep { + T remove(Collection entitiesToRemove); + } + + interface SetStep { + T set(Collection entitiesToSet); + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/PNSortKey.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/PNSortKey.java new file mode 100644 index 000000000..1a70f8b24 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/PNSortKey.java @@ -0,0 +1,63 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import lombok.AccessLevel; +import lombok.Getter; + +import static com.pubnub.api.endpoints.objects_api.utils.PNSortKey.Dir.ASC; +import static com.pubnub.api.endpoints.objects_api.utils.PNSortKey.Dir.DESC; + +public class PNSortKey { + public enum Dir { + ASC("asc"), DESC("desc"); + + @Getter(AccessLevel.PACKAGE) + private final String dir; + + Dir(String dir) { + this.dir = dir; + } + } + + public enum Key { + ID("id"), NAME("name"), UPDATED("updated"); + + @Getter(AccessLevel.PACKAGE) + private final String fieldName; + + Key(String fieldName) { + this.fieldName = fieldName; + } + } + + @Getter(AccessLevel.PACKAGE) + private final Dir dir; + + @Getter(AccessLevel.PACKAGE) + private final Key key; + + private PNSortKey(Key key, Dir dir) { + this.key = key; + this.dir = dir; + } + + public static PNSortKey of(Key key, Dir dir) { + return new PNSortKey(key, dir); + } + + public static PNSortKey of(Key key) { + return new PNSortKey(key, ASC); + } + + public static PNSortKey asc(Key key) { + return new PNSortKey(key, ASC); + } + + public static PNSortKey desc(Key key) { + return new PNSortKey(key, DESC); + } + + public String toSortParameter() { + return key.fieldName + ":" + dir.dir; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Pager.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Pager.java new file mode 100644 index 000000000..7072d02f0 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Pager.java @@ -0,0 +1,64 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.models.consumer.PNPage; + +import java.util.HashMap; +import java.util.Map; + +public class Pager implements ParameterEnricher { + static final String START_PARAM_NAME = "start"; + static final String END_PARAM_NAME = "end"; + + public interface PagingAware> { + T page(PNPage page); + } + + public interface HavingPager> extends PagingAware, HavingCompositeParameterEnricher { + @Override + default T page(PNPage page) { + getCompositeParameterEnricher().getPager().setPage(page); + return (T) this; + } + } + + private PNPage page; + + public void setPage(final PNPage page) { + this.page = page; + } + + @Override + public Map enrichParameters(Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (page != null) { + if (page instanceof PNPage.Next) { + enrichedMap.put(START_PARAM_NAME, page.getHash()); + } + if (page instanceof PNPage.Previous) { + enrichedMap.put(END_PARAM_NAME, page.getHash()); + } + } + return enrichedMap; + } + + @Override + public void validateParameters() throws PubNubException { + if (page != null) { + if (page.getHash() == null || page.getHash().isEmpty()) { + if (page instanceof PNPage.Next) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PAGINATION_NEXT_OUT_OF_BOUNDS) + .build(); + } + if (page instanceof PNPage.Previous) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PAGINATION_PREV_OUT_OF_BOUNDS) + .build(); + } + } + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ParameterEnricher.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ParameterEnricher.java new file mode 100644 index 000000000..d64094d95 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/ParameterEnricher.java @@ -0,0 +1,11 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.PubNubException; + +import java.util.Map; + +public interface ParameterEnricher { + Map enrichParameters(Map baseParams); + default void validateParameters() throws PubNubException { + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Sorter.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Sorter.java new file mode 100644 index 000000000..8561522e2 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/Sorter.java @@ -0,0 +1,42 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.endpoints.Endpoint; + +import java.util.*; + +public class Sorter implements ParameterEnricher { + static final String SORT_PARAM_NAME = "sort"; + + public interface SortingAware> { + T sort(PNSortKey... sortKeys); + } + + public interface HavingSorter> extends SortingAware, HavingCompositeParameterEnricher { + @Override + default T sort(final PNSortKey... sortKeys) { + getCompositeParameterEnricher().getSorter().addSortKeys(Arrays.asList(sortKeys)); + return (T) this; + } + } + + private List sortKeyList = new ArrayList<>(); + + public void addSortKeys(final List sortKeys) { + this.sortKeyList = sortKeys; + } + + @Override + public Map enrichParameters(final Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (!sortKeyList.isEmpty()) { + final List sortKeys = new ArrayList<>(); + for (final PNSortKey sortKey : sortKeyList) { + sortKeys.add(sortKey.toSortParameter()); + } + final String sortKeysJoined = PubNubUtil.joinString(sortKeys, ","); + enrichedMap.put(SORT_PARAM_NAME, sortKeysJoined); + } + return enrichedMap; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/utils/TotalCounter.java b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/TotalCounter.java new file mode 100644 index 000000000..3c33337e0 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/utils/TotalCounter.java @@ -0,0 +1,39 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.endpoints.Endpoint; + +import java.util.HashMap; +import java.util.Map; + +public class TotalCounter implements ParameterEnricher { + static final String COUNT_PARAM_NAME = "count"; + + public interface TotalCountAware> { + T includeTotalCount(boolean includeTotalCount); + } + + public interface HavingTotalCounter> extends TotalCountAware, + HavingCompositeParameterEnricher { + @Override + default T includeTotalCount(boolean includeTotalCount) { + getCompositeParameterEnricher().getTotalCounter().setIncludeTotalCount(includeTotalCount); + return (T) this; + } + } + + private Boolean includeTotalCount; + + public void setIncludeTotalCount(boolean includeTotalCount) { + this.includeTotalCount = includeTotalCount; + } + + @Override + public Map enrichParameters(Map baseParams) { + final Map enrichedMap = new HashMap<>(baseParams); + if (includeTotalCount != null) { + enrichedMap.put(COUNT_PARAM_NAME, includeTotalCount.toString()); + } + return enrichedMap; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetAllUUIDMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetAllUUIDMetadata.java new file mode 100644 index 000000000..fe6e20b1d --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetAllUUIDMetadata.java @@ -0,0 +1,84 @@ +package com.pubnub.api.endpoints.objects_api.uuid; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.ObjectApiEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.uuid.PNGetAllUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetAllUUIDMetadata + extends ObjectApiEndpoint, PNGetAllUUIDMetadataResult> implements + CustomIncludeAware, + ListCapabilitiesAware { + + public GetAllUUIDMetadata(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static GetAllUUIDMetadata create(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new GetAllUUIDMetadataCommand(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, + tokenManager); + } +} + +final class GetAllUUIDMetadataCommand extends GetAllUUIDMetadata implements + HavingCustomInclude, HavingListCapabilites { + + GetAllUUIDMetadataCommand(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getUuidMetadataService() + .getUUIDMetadata(getPubnub().getConfiguration().getSubscribeKey(), effectiveParams); + } + + @Override + protected PNGetAllUUIDMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNGetAllUUIDMetadataResult(input.body()); + } else { + return new PNGetAllUUIDMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetAllUuidMetadataOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetUUIDMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetUUIDMetadata.java new file mode 100644 index 000000000..65fb5cf20 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/GetUUIDMetadata.java @@ -0,0 +1,78 @@ +package com.pubnub.api.endpoints.objects_api.uuid; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.uuid.PNGetUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Map; + +public abstract class GetUUIDMetadata extends UUIDEndpoint, PNGetUUIDMetadataResult> + implements CustomIncludeAware { + + public GetUUIDMetadata(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static GetUUIDMetadata create(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new GetUUIDMetadataCommand(pubnubInstance, telemetry, retrofitInstance, + compositeParameterEnricher, tokenManager); + } +} + +final class GetUUIDMetadataCommand extends GetUUIDMetadata implements Include.HavingCustomInclude { + GetUUIDMetadataCommand(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getUuidMetadataService() + .getUUIDMetadata(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), effectiveParams); + } + + @Override + protected PNGetUUIDMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNGetUUIDMetadataResult(input.body()); + } else { + return new PNGetUUIDMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetUuidMetadataOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/RemoveUUIDMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/RemoveUUIDMetadata.java new file mode 100644 index 000000000..b9a24ee43 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/RemoveUUIDMetadata.java @@ -0,0 +1,47 @@ +package com.pubnub.api.endpoints.objects_api.uuid; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.uuid.PNRemoveUUIDMetadataResult; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.Map; + +public class RemoveUUIDMetadata extends UUIDEndpoint, PNRemoveUUIDMetadataResult> { + + public RemoveUUIDMetadata(final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, CompositeParameterEnricher.createDefault(), tokenManager); + } + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + return getRetrofit() + .getUuidMetadataService() + .deleteUUIDMetadata(getPubnub().getConfiguration().getSubscribeKey(), effectiveUuid(), Collections.emptyMap()); + } + + @Override + protected PNRemoveUUIDMetadataResult createResponse(Response> input) + throws PubNubException { + return new PNRemoveUUIDMetadataResult(input.body()); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveUuidMetadataOperation; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadata.java b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadata.java new file mode 100644 index 000000000..72e0f7301 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadata.java @@ -0,0 +1,133 @@ +package com.pubnub.api.endpoints.objects_api.uuid; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.UUIDEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.server.objects_api.SetUUIDMetadataPayload; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.HashMap; +import java.util.Map; + +public abstract class SetUUIDMetadata extends UUIDEndpoint, PNSetUUIDMetadataResult> + implements CustomIncludeAware { + SetUUIDMetadata( + final PubNub pubnubInstance, + final TelemetryManager telemetry, + final RetrofitManager retrofitInstance, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, tokenManager); + } + + public static SetUUIDMetadata create(final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final TokenManager tokenManager) { + final CompositeParameterEnricher compositeParameterEnricher = CompositeParameterEnricher.createDefault(); + return new SetUUIDMetadataCommand(pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + } + + public abstract SetUUIDMetadata name(String name); + public abstract SetUUIDMetadata email(String email); + public abstract SetUUIDMetadata profileUrl(String profileUrl); + public abstract SetUUIDMetadata externalId(String externalId); + public abstract SetUUIDMetadata custom(Map custom); +} + +final class SetUUIDMetadataCommand extends SetUUIDMetadata implements HavingCustomInclude { + private String name; + private String email; + private String profileUrl; + private String externalId; + private Map custom; + + SetUUIDMetadataCommand(final PubNub pubNub, + final TelemetryManager telemetryManager, + final RetrofitManager retrofitManager, + final CompositeParameterEnricher compositeParameterEnricher, + final TokenManager tokenManager) { + super(pubNub, telemetryManager, retrofitManager, compositeParameterEnricher, tokenManager); + } + + + + @Override + protected Call> executeCommand(final Map effectiveParams) + throws PubNubException { + //This is workaround to accept custom maps that are instances of anonymous classes not handled by gson + final HashMap customHashMap = new HashMap(); + if (custom != null) { + customHashMap.putAll(custom); + } + + final SetUUIDMetadataPayload setUUIDMetadataPayload = new SetUUIDMetadataPayload(name, email, externalId, + profileUrl, customHashMap); + + return getRetrofit() + .getUuidMetadataService() + .setUUIDsMetadata(getPubnub().getConfiguration().getSubscribeKey(), + effectiveUuid(), setUUIDMetadataPayload, effectiveParams); + } + + @Override + protected PNSetUUIDMetadataResult createResponse(Response> input) + throws PubNubException { + if (input.body() != null) { + return new PNSetUUIDMetadataResult(input.body()); + } else { + return new PNSetUUIDMetadataResult(); + } + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSetUuidMetadataOperation; + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } + + @Override + public SetUUIDMetadata name(String name) { + this.name = name; + return this; + } + + @Override + public SetUUIDMetadata email(String email) { + this.email = email; + return this; + } + + @Override + public SetUUIDMetadata profileUrl(String profileUrl) { + this.profileUrl = profileUrl; + return this; + } + + @Override + public SetUUIDMetadata externalId(String externalId) { + this.externalId = externalId; + return this; + } + + @Override + public SetUUIDMetadata custom(Map custom) { + this.custom = custom; + return this; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/GetState.java b/src/main/java/com/pubnub/api/endpoints/presence/GetState.java new file mode 100644 index 000000000..cff39cce2 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/GetState.java @@ -0,0 +1,104 @@ +package com.pubnub.api.endpoints.presence; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.presence.PNGetStateResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.*; + +@Accessors(chain = true, fluent = true) +public class GetState extends Endpoint, PNGetStateResult> { + + @Setter + private List channels; + @Setter + private List channelGroups; + @Setter + private String uuid; + + public GetState(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channels.size() == 0 && channelGroups.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_AND_GROUP_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) { + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + String channelCSV = channels.size() > 0 ? PubNubUtil.joinString(channels, ",") : ","; + + String selectedUUID = uuid != null ? uuid : this.getPubnub().getConfiguration().getUuid(); + + return this.getRetrofit().getPresenceService().getState( + this.getPubnub().getConfiguration().getSubscribeKey(), channelCSV, selectedUUID, params); + } + + @Override + protected PNGetStateResult createResponse(Response> input) throws PubNubException { + Map stateMappings = new HashMap<>(); + MapperManager mapper = getPubnub().getMapper(); + + if (channels.size() == 1 && channelGroups.size() == 0) { + stateMappings.put(channels.get(0), input.body().getPayload()); + } else { + Iterator> it = mapper.getObjectIterator(input.body().getPayload()); + while (it.hasNext()) { + Map.Entry stateMapping = it.next(); + stateMappings.put(stateMapping.getKey(), stateMapping.getValue()); + } + } + + return PNGetStateResult.builder().stateByUUID(stateMappings).build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNGetState; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/Heartbeat.java b/src/main/java/com/pubnub/api/endpoints/presence/Heartbeat.java new file mode 100644 index 000000000..ed7b78901 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/Heartbeat.java @@ -0,0 +1,100 @@ +package com.pubnub.api.endpoints.presence; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class Heartbeat extends Endpoint { + + @Setter + private List channels; + @Setter + private List channelGroups; + @Setter + private Object state; + + public Heartbeat(PubNub pubnub, TelemetryManager telemetryManager, RetrofitManager retrofit, TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channels.size() == 0 && channelGroups.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_AND_GROUP_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) throws PubNubException { + params.put("heartbeat", String.valueOf(this.getPubnub().getConfiguration().getPresenceTimeout())); + + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + String channelsCSV; + + if (channels.size() > 0) { + channelsCSV = PubNubUtil.joinString(channels, ","); + } else { + channelsCSV = ","; + } + + if (state != null) { + String stringifiedState = this.getPubnub().getMapper().toJson(state); + stringifiedState = PubNubUtil.urlEncode(stringifiedState); + params.put("state", stringifiedState); + } + + params.putAll(encodeParams(params)); + + return this.getRetrofit().getPresenceService().heartbeat(this.getPubnub().getConfiguration().getSubscribeKey(), channelsCSV, params); + } + + @Override + protected Boolean createResponse(Response input) throws PubNubException { + return true; + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNHeartbeatOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/HereNow.java b/src/main/java/com/pubnub/api/endpoints/presence/HereNow.java new file mode 100644 index 000000000..6463972d2 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/HereNow.java @@ -0,0 +1,205 @@ +package com.pubnub.api.endpoints.presence; + + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.presence.PNHereNowChannelData; +import com.pubnub.api.models.consumer.presence.PNHereNowOccupantData; +import com.pubnub.api.models.consumer.presence.PNHereNowResult; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.*; + +@Accessors(chain = true, fluent = true) +public class HereNow extends Endpoint, PNHereNowResult> { + @Setter + private List channels; + @Setter + private List channelGroups; + @Setter + private Boolean includeState; + @Setter + private Boolean includeUUIDs; + + public HereNow(PubNub pubnubInstance, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnubInstance, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) { + + if (includeState == null) { + includeState = false; + } + + if (includeUUIDs == null) { + includeUUIDs = true; + } + + String channelCSV; + + if (includeState) { + params.put("state", "1"); + } + if (!includeUUIDs) { + params.put("disable_uuids", "1"); + } + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + if (channels.size() > 0) { + channelCSV = PubNubUtil.joinString(channels, ","); + } else { + channelCSV = ","; + } + + if (channels.size() > 0 || channelGroups.size() > 0) { + return this.getRetrofit().getPresenceService().hereNow(this.getPubnub().getConfiguration().getSubscribeKey(), channelCSV, params); + } else { + return this.getRetrofit().getPresenceService().globalHereNow(this.getPubnub().getConfiguration().getSubscribeKey(), params); + } + } + + @Override + protected PNHereNowResult createResponse(Response> input) { + PNHereNowResult herenowData; + + if (channels.isEmpty() && channelGroups.isEmpty()) { + herenowData = parseMultipleChannelResponse(input.body().getPayload()); + } else { + if (channels.size() > 1 || channelGroups.size() > 0) { + herenowData = parseMultipleChannelResponse(input.body().getPayload()); + } else { + herenowData = parseSingleChannelResponse(input.body()); + } + } + + return herenowData; + } + + private PNHereNowResult parseSingleChannelResponse(Envelope input) { + PNHereNowResult hereNowData = PNHereNowResult.builder() + .totalChannels(1) + .channels(new HashMap()) + .totalOccupancy(input.getOccupancy()) + .build(); + + PNHereNowChannelData.PNHereNowChannelDataBuilder hereNowChannelData = PNHereNowChannelData.builder() + .channelName(channels.get(0)) + .occupancy(input.getOccupancy()); + + if (includeUUIDs) { + hereNowChannelData.occupants(prepareOccupantData(input.getUuids())); + hereNowData.getChannels().put(channels.get(0), hereNowChannelData.build()); + } + + return hereNowData; + } + + private PNHereNowResult parseMultipleChannelResponse(JsonElement input) { + MapperManager mapper = getPubnub().getMapper(); + PNHereNowResult hereNowData = PNHereNowResult.builder() + .channels(new HashMap()) + .totalChannels(mapper.elementToInt(input, "total_channels")) + .totalOccupancy(mapper.elementToInt(input, "total_occupancy")) + .build(); + + Iterator> it = mapper.getObjectIterator(input, "channels"); + while (it.hasNext()) { + Map.Entry entry = it.next(); + + PNHereNowChannelData.PNHereNowChannelDataBuilder hereNowChannelData = PNHereNowChannelData.builder() + .channelName(entry.getKey()) + .occupancy(mapper.elementToInt(entry.getValue(), "occupancy")); + + if (includeUUIDs) { + hereNowChannelData.occupants(prepareOccupantData(mapper.getField(entry.getValue(), "uuids"))); + } else { + hereNowChannelData.occupants(null); + } + + hereNowData.getChannels().put(entry.getKey(), hereNowChannelData.build()); + } + + return hereNowData; + } + + private List prepareOccupantData(JsonElement input) { + List occupantsResults = new ArrayList<>(); + MapperManager mapper = getPubnub().getMapper(); + + if (includeState != null && includeState) { + Iterator it = mapper.getArrayIterator(input); + while (it.hasNext()) { + JsonElement occupant = it.next(); + PNHereNowOccupantData.PNHereNowOccupantDataBuilder hereNowOccupantData = + PNHereNowOccupantData.builder(); + hereNowOccupantData.uuid(mapper.elementToString(occupant, "uuid")); + hereNowOccupantData.state(mapper.getField(occupant, "state")); + + occupantsResults.add(hereNowOccupantData.build()); + } + } else { + Iterator it = mapper.getArrayIterator(input); + while (it.hasNext()) { + JsonElement occupant = it.next(); + PNHereNowOccupantData.PNHereNowOccupantDataBuilder hereNowOccupantData = + PNHereNowOccupantData.builder(); + hereNowOccupantData.uuid(mapper.elementToString(occupant)); + hereNowOccupantData.state(null); + + occupantsResults.add(hereNowOccupantData.build()); + } + } + + return occupantsResults; + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNHereNowOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/Leave.java b/src/main/java/com/pubnub/api/endpoints/presence/Leave.java new file mode 100644 index 000000000..6460ca601 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/Leave.java @@ -0,0 +1,91 @@ +package com.pubnub.api.endpoints.presence; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.Envelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class Leave extends Endpoint { + @Setter + private List channels; + @Setter + private List channelGroups; + + public Leave(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channels.size() == 0 && channelGroups.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_AND_GROUP_MISSING).build(); + } + } + + @Override + protected Call doWork(Map params) { + String channelCSV; + + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + if (channels.size() > 0) { + channelCSV = PubNubUtil.joinString(channels, ","); + } else { + channelCSV = ","; + } + + return this.getRetrofit().getPresenceService().leave(this.getPubnub().getConfiguration().getSubscribeKey(), + channelCSV, params); + } + + @Override + protected Boolean createResponse(Response input) throws PubNubException { + return true; + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNUnsubscribeOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/SetState.java b/src/main/java/com/pubnub/api/endpoints/presence/SetState.java new file mode 100644 index 000000000..7e2b2c9cc --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/SetState.java @@ -0,0 +1,134 @@ +package com.pubnub.api.endpoints.presence; + +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.builder.dto.StateOperation; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.SubscriptionManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.presence.PNSetStateResult; +import com.pubnub.api.models.server.Envelope; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +@Accessors(chain = true, fluent = true) +public class SetState extends Endpoint, PNSetStateResult> { + + @Getter(AccessLevel.NONE) + private SubscriptionManager subscriptionManager; + + @Setter + private List channels; + @Setter + private List channelGroups; + @Setter + private Object state; + @Setter + private String uuid; + + + public SetState(PubNub pubnub, + SubscriptionManager subscriptionManagerInstance, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + this.subscriptionManager = subscriptionManagerInstance; + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (state == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_STATE_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channels.size() == 0 && channelGroups.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_AND_GROUP_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) throws PubNubException { + String selectedUUID = uuid != null ? uuid : this.getPubnub().getConfiguration().getUuid(); + String stringifiedState; + + // only store the state change if we are modifying it for ourselves. + if (selectedUUID.equals(this.getPubnub().getConfiguration().getUuid())) { + StateOperation stateOperation = StateOperation.builder() + .state(state) + .channels(channels) + .channelGroups(channelGroups) + .build(); + subscriptionManager.adaptStateBuilder(stateOperation); + } + + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + stringifiedState = this.getPubnub().getMapper().toJson(state); + + stringifiedState = PubNubUtil.urlEncode(stringifiedState); + params.put("state", stringifiedState); + + params.putAll(encodeParams(params)); + + String channelCSV = channels.size() > 0 ? PubNubUtil.joinString(channels, ",") : ","; + + return this.getRetrofit().getPresenceService().setState( + this.getPubnub().getConfiguration().getSubscribeKey(), channelCSV, selectedUUID, params); + } + + @Override + protected PNSetStateResult createResponse(Response> input) throws PubNubException { + + if (input.body() == null || input.body().getPayload() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + PNSetStateResult.PNSetStateResultBuilder pnSetStateResult = PNSetStateResult.builder() + .state(input.body().getPayload()); + + return pnSetStateResult.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSetStateOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/presence/WhereNow.java b/src/main/java/com/pubnub/api/endpoints/presence/WhereNow.java new file mode 100644 index 000000000..64bc0d957 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/presence/WhereNow.java @@ -0,0 +1,81 @@ +package com.pubnub.api.endpoints.presence; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.presence.PNWhereNowResult; +import com.pubnub.api.models.server.Envelope; +import com.pubnub.api.models.server.presence.WhereNowPayload; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class WhereNow extends Endpoint, PNWhereNowResult> { + + @Setter + private String uuid; + + public WhereNow(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) { + return this.getRetrofit().getPresenceService().whereNow(this.getPubnub().getConfiguration().getSubscribeKey(), + this.uuid != null ? this.uuid : this.getPubnub().getConfiguration().getUuid(), params); + } + + @Override + protected PNWhereNowResult createResponse(Response> input) throws PubNubException { + if (input.body() == null || input.body().getPayload() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + PNWhereNowResult pnPresenceWhereNowResult = PNWhereNowResult.builder() + .channels(input.body().getPayload().getChannels()) + .build(); + + return pnPresenceWhereNowResult; + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNWhereNowOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/pubsub/Publish.java b/src/main/java/com/pubnub/api/endpoints/pubsub/Publish.java new file mode 100644 index 000000000..b37110f32 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/pubsub/Publish.java @@ -0,0 +1,163 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.PublishSequenceManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.vendor.Crypto; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class Publish extends Endpoint, PNPublishResult> { + + @Setter + private Object message; + @Setter + private String channel; + @Setter + private Boolean shouldStore; + @Setter + private Boolean usePOST; + @Setter + private Object meta; + @Setter + private Boolean replicate; + @Setter + private Integer ttl; + + private PublishSequenceManager publishSequenceManager; + + public Publish(PubNub pubnub, + PublishSequenceManager providedPublishSequenceManager, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + + this.publishSequenceManager = providedPublishSequenceManager; + this.replicate = true; + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (message == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_MISSING).build(); + } + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getPublishKey() == null || this.getPubnub().getConfiguration().getPublishKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUBLISH_KEY_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) throws PubNubException { + MapperManager mapper = this.getPubnub().getMapper(); + + String stringifiedMessage = mapper.toJson(message); + + if (meta != null) { + String stringifiedMeta = mapper.toJson(meta); + stringifiedMeta = PubNubUtil.urlEncode(stringifiedMeta); + params.put("meta", stringifiedMeta); + } + + if (shouldStore != null) { + if (shouldStore) { + params.put("store", "1"); + } else { + params.put("store", "0"); + } + } + + if (ttl != null) { + params.put("ttl", String.valueOf(ttl)); + } + + params.put("seqn", String.valueOf(publishSequenceManager.getNextSequence())); + + if (!replicate) { + params.put("norep", "true"); + } + + if (this.getPubnub().getConfiguration().getCipherKey() != null) { + Crypto crypto = new Crypto(this.getPubnub().getConfiguration().getCipherKey(), this.getPubnub().getConfiguration().isUseRandomInitializationVector()); + stringifiedMessage = crypto.encrypt(stringifiedMessage).replace("\n", ""); + } + + params.putAll(encodeParams(params)); + + if (usePOST != null && usePOST) { + Object payloadToSend; + + if (this.getPubnub().getConfiguration().getCipherKey() != null) { + payloadToSend = stringifiedMessage; + } else { + payloadToSend = message; + } + + return this.getRetrofit().getPublishService().publishWithPost(this.getPubnub().getConfiguration().getPublishKey(), + this.getPubnub().getConfiguration().getSubscribeKey(), + channel, payloadToSend, params); + } else { + + if (this.getPubnub().getConfiguration().getCipherKey() != null) { + stringifiedMessage = "\"".concat(stringifiedMessage).concat("\""); + } + + stringifiedMessage = PubNubUtil.urlEncode(stringifiedMessage); + + return this.getRetrofit().getPublishService().publish(this.getPubnub().getConfiguration().getPublishKey(), + this.getPubnub().getConfiguration().getSubscribeKey(), + channel, stringifiedMessage, params); + } + } + + @Override + protected PNPublishResult createResponse(Response> input) throws PubNubException { + PNPublishResult.PNPublishResultBuilder pnPublishResult = PNPublishResult.builder(); + pnPublishResult.timetoken(Long.valueOf(input.body().get(2).toString())); + + return pnPublishResult.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNPublishOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/pubsub/Signal.java b/src/main/java/com/pubnub/api/endpoints/pubsub/Signal.java new file mode 100644 index 000000000..fc05a4456 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/pubsub/Signal.java @@ -0,0 +1,105 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNPublishResult; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Accessors(chain = true, fluent = true) +public class Signal extends Endpoint, PNPublishResult> { + + @Setter + private Object message; + + @Setter + private String channel; + + public Signal(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return Collections.singletonList(channel); + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (message == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_MESSAGE_MISSING).build(); + } + if (channel == null || channel.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getPublishKey() == null || this.getPubnub() + .getConfiguration() + .getPublishKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUBLISH_KEY_MISSING).build(); + } + } + + @Override + protected Call> doWork(Map params) throws PubNubException { + MapperManager mapper = this.getPubnub().getMapper(); + + String stringifiedMessage = mapper.toJson(message); + + params.putAll(encodeParams(params)); + + stringifiedMessage = PubNubUtil.urlEncode(stringifiedMessage); + + return this.getRetrofit().getSignalService().signal(this.getPubnub().getConfiguration().getPublishKey(), + this.getPubnub().getConfiguration().getSubscribeKey(), + channel, stringifiedMessage, params); + + } + + @Override + protected PNPublishResult createResponse(Response> input) throws PubNubException { + PNPublishResult.PNPublishResultBuilder pnPublishResult = PNPublishResult.builder(); + pnPublishResult.timetoken(Long.valueOf(input.body().get(2).toString())); + + return pnPublishResult.build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSignalOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/pubsub/Subscribe.java b/src/main/java/com/pubnub/api/endpoints/pubsub/Subscribe.java new file mode 100644 index 000000000..b23cd98c6 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/pubsub/Subscribe.java @@ -0,0 +1,198 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.server.SubscribeEnvelope; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; +import retrofit2.Response; + +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Supports calling of the subscribe endpoints and deconstructs the response to POJO's. + */ +@Slf4j +@Accessors(chain = true, fluent = true) +public class Subscribe extends Endpoint { + static final int RATE_LIMIT_EXCEEDED = 429; + static final int URI_TOO_LONG = 414; + /** + * List of channels that will be called to subscribe. + */ + @Setter + private List channels; + /** + * List of channel groups that will be called with subscribe. + */ + @Setter + private List channelGroups; + + /** + * timetoken to subscribe with 0 for initial subscribe. + */ + @Setter + private Long timetoken; + + /** + * filterExpression used as part of PubSub V2 specification to filter on message. + */ + @Setter + private String filterExpression; + + /** + * region is used as part of PubSub V2 to help the server route traffic to best data center. + */ + @Setter + private String region; + + @Setter + private Object state; + + /** + * Create a new Subscribe instance endpoint. + * + * @param pubnub supplied pubnub instance. + */ + public Subscribe(PubNub pubnub, RetrofitManager retrofit, TokenManager tokenManager) { + super(pubnub, null, retrofit, tokenManager); + channels = new ArrayList<>(); + channelGroups = new ArrayList<>(); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return channelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (channels.size() == 0 && channelGroups.size() == 0) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_AND_GROUP_MISSING).build(); + } + } + + @Override + public void async(@NotNull PNCallback callback) { + super.async(new PNCallback() { + @Override + public void onResponse(@Nullable SubscribeEnvelope result, @NotNull PNStatus status) { + if (status.isError()) { + final PNStatus maybeNewStatus; + if (status.getStatusCode() == HttpURLConnection.HTTP_BAD_REQUEST && status.getErrorData() + .getInformation() + .contains("Filter syntax error")) { + maybeNewStatus = status.toBuilder() + .category(PNStatusCategory.PNMalformedFilterExpressionCategory) + .build(); + } else if (status.getStatusCode() == URI_TOO_LONG) { + maybeNewStatus = status.toBuilder() + .category(PNStatusCategory.PNURITooLongCategory) + .build(); + } else if (status.getStatusCode() == RATE_LIMIT_EXCEEDED) { + maybeNewStatus = status.toBuilder() + .category(PNStatusCategory.PNRateLimitExceededCategory) + .build(); + } else { + maybeNewStatus = status; + } + callback.onResponse(result, maybeNewStatus); + } else { + callback.onResponse(result, status); + } + + } + }); + } + + @Override + protected Call doWork(Map params) throws PubNubException { + MapperManager mapper = this.getPubnub().getMapper(); + + String channelCSV; + + if (channelGroups.size() > 0) { + params.put("channel-group", PubNubUtil.joinString(channelGroups, ",")); + } + + if (filterExpression != null && filterExpression.length() > 0) { + params.put("filter-expr", PubNubUtil.urlEncode(filterExpression)); + } + + if (timetoken != null) { + params.put("tt", timetoken.toString()); + } + + if (region != null) { + params.put("tr", region); + } + + if (channels.size() > 0) { + channelCSV = PubNubUtil.joinString(channels, ","); + } else { + channelCSV = ","; + } + + params.put("heartbeat", String.valueOf(this.getPubnub().getConfiguration().getPresenceTimeout())); + + if (state != null) { + String stringifiedState = mapper.toJson(state); + stringifiedState = PubNubUtil.urlEncode(stringifiedState); + params.put("state", stringifiedState); + } + + params.putAll(encodeParams(params)); + + return this.getRetrofit().getSubscribeService() + .subscribe(this.getPubnub().getConfiguration().getSubscribeKey(), channelCSV, params); + } + + @Override + protected SubscribeEnvelope createResponse(Response input) throws PubNubException { + + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + return input.body(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNSubscribeOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/push/AddChannelsToPush.java b/src/main/java/com/pubnub/api/endpoints/push/AddChannelsToPush.java new file mode 100644 index 000000000..e9805786d --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/push/AddChannelsToPush.java @@ -0,0 +1,120 @@ +package com.pubnub.api.endpoints.push; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.push.PNPushAddChannelResult; + +import java.util.List; +import java.util.Map; + +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +@Accessors(chain = true, fluent = true) +public class AddChannelsToPush extends Endpoint, PNPushAddChannelResult> { + + @Setter + private PNPushType pushType; + @Setter + private List channels; + @Setter + private String deviceId; + @Setter + private PNPushEnvironment environment; + @Setter + private String topic; + + public AddChannelsToPush(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (pushType == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TYPE_MISSING).build(); + } + if (deviceId == null || deviceId.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_DEVICE_ID_MISSING).build(); + } + if (channels == null || channels.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (pushType == PNPushType.APNS2) { + if (topic == null || topic.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TOPIC_MISSING).build(); + } + if (environment == null) { + environment = PNPushEnvironment.DEVELOPMENT; + } + } + } + + @Override + protected Call> doWork(Map baseParams) throws PubNubException { + baseParams.put("add", PubNubUtil.joinString(channels, ",")); + + if (pushType != PNPushType.APNS2) { + baseParams.put("type", pushType.toString()); + + return this.getRetrofit().getPushService().modifyChannelsForDevice( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + baseParams); + } else { + baseParams.put("environment", environment.name().toLowerCase()); + baseParams.put("topic", topic); + + return this.getRetrofit().getPushService().modifyChannelsForDeviceApns2( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + baseParams); + } + } + + @Override + protected PNPushAddChannelResult createResponse(Response> input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + return PNPushAddChannelResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNPushNotificationEnabledChannelsOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/push/ListPushProvisions.java b/src/main/java/com/pubnub/api/endpoints/push/ListPushProvisions.java new file mode 100644 index 000000000..898586eb3 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/push/ListPushProvisions.java @@ -0,0 +1,109 @@ +package com.pubnub.api.endpoints.push; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult; + +import java.util.List; +import java.util.Map; + +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +@Accessors(chain = true, fluent = true) +public class ListPushProvisions extends Endpoint, PNPushListProvisionsResult> { + + @Setter + private PNPushType pushType; + @Setter + private String deviceId; + @Setter + private PNPushEnvironment environment; + @Setter + private String topic; + + public ListPushProvisions(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (pushType == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TYPE_MISSING).build(); + } + if (deviceId == null || deviceId.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_DEVICE_ID_MISSING).build(); + } + if (pushType == PNPushType.APNS2) { + if (topic == null || topic.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TOPIC_MISSING).build(); + } + if (environment == null) { + environment = PNPushEnvironment.DEVELOPMENT; + } + } + } + + @Override + protected Call> doWork(Map params) throws PubNubException { + if (pushType != PNPushType.APNS2) { + params.put("type", pushType.toString()); + return this.getRetrofit().getPushService().listChannelsForDevice( + this.getPubnub().getConfiguration().getSubscribeKey(), deviceId, params); + } else { + params.put("environment", environment.name().toLowerCase()); + params.put("topic", topic); + return this.getRetrofit().getPushService().listChannelsForDeviceApns2( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + params); + } + } + + @Override + protected PNPushListProvisionsResult createResponse(Response> input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + return PNPushListProvisionsResult.builder().channels(input.body()).build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNPushNotificationEnabledChannelsOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.java b/src/main/java/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.java new file mode 100644 index 000000000..463f38a13 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/push/RemoveAllPushChannelsForDevice.java @@ -0,0 +1,112 @@ +package com.pubnub.api.endpoints.push; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.push.PNPushRemoveAllChannelsResult; + +import java.util.List; +import java.util.Map; + +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +@Accessors(chain = true, fluent = true) +public class RemoveAllPushChannelsForDevice extends Endpoint, PNPushRemoveAllChannelsResult> { + + @Setter + private PNPushType pushType; + @Setter + private String deviceId; + @Setter + private PNPushEnvironment environment; + @Setter + private String topic; + + public RemoveAllPushChannelsForDevice(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (pushType == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TYPE_MISSING).build(); + } + if (deviceId == null || deviceId.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_DEVICE_ID_MISSING).build(); + } + if (pushType == PNPushType.APNS2) { + if (topic == null || topic.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TOPIC_MISSING).build(); + } + if (environment == null) { + environment = PNPushEnvironment.DEVELOPMENT; + } + } + } + + + @Override + protected Call> doWork(Map params) throws PubNubException { + if (pushType != PNPushType.APNS2) { + params.put("type", pushType.toString()); + return this.getRetrofit().getPushService().removeAllChannelsForDevice( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + params); + } else { + params.put("environment", environment.name().toLowerCase()); + params.put("topic", topic); + return this.getRetrofit().getPushService().removeAllChannelsForDeviceApns2( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + params); + } + } + + @Override + protected PNPushRemoveAllChannelsResult createResponse(Response> input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + + return PNPushRemoveAllChannelsResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemoveAllPushNotificationsOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + +} diff --git a/src/main/java/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.java b/src/main/java/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.java new file mode 100644 index 000000000..4d1d89a5b --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/push/RemoveChannelsFromPush.java @@ -0,0 +1,118 @@ +package com.pubnub.api.endpoints.push; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult; + +import java.util.List; +import java.util.Map; + +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +@Accessors(chain = true, fluent = true) +public class RemoveChannelsFromPush extends Endpoint, PNPushRemoveChannelResult> { + + @Setter + private PNPushType pushType; + @Setter + private List channels; + @Setter + private String deviceId; + @Setter + private PNPushEnvironment environment; + @Setter + private String topic; + + public RemoveChannelsFromPush(PubNub pubnub, + TelemetryManager telemetryManager, + RetrofitManager retrofit, + TokenManager tokenManager) { + super(pubnub, telemetryManager, retrofit, tokenManager); + } + + @Override + protected List getAffectedChannels() { + return channels; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSubscribeKey() == null + || this.getPubnub().getConfiguration().getSubscribeKey().isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (pushType == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TYPE_MISSING).build(); + } + if (deviceId == null || deviceId.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_DEVICE_ID_MISSING).build(); + } + if (channels == null || channels.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING).build(); + } + if (pushType == PNPushType.APNS2) { + if (topic == null || topic.isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PUSH_TOPIC_MISSING).build(); + } + if (environment == null) { + environment = PNPushEnvironment.DEVELOPMENT; + } + } + } + + @Override + protected Call> doWork(Map baseParams) throws PubNubException { + baseParams.put("remove", PubNubUtil.joinString(channels, ",")); + + if (pushType != PNPushType.APNS2) { + baseParams.put("type", pushType.toString()); + return this.getRetrofit().getPushService().modifyChannelsForDevice( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + baseParams); + } else { + baseParams.put("environment", environment.name().toLowerCase()); + baseParams.put("topic", topic); + + return this.getRetrofit().getPushService().modifyChannelsForDeviceApns2( + this.getPubnub().getConfiguration().getSubscribeKey(), + deviceId, + baseParams); + } + } + + @Override + protected PNPushRemoveChannelResult createResponse(Response> input) throws PubNubException { + if (input.body() == null) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR).build(); + } + return PNPushRemoveChannelResult.builder().build(); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNRemovePushNotificationsFromChannelsOperation; + } + + @Override + protected boolean isAuthRequired() { + return true; + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteAction.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteAction.java new file mode 100644 index 000000000..36c163df6 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteAction.java @@ -0,0 +1,121 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.models.consumer.PNStatus; +import org.jetbrains.annotations.NotNull; + +public class ComposableRemoteAction implements RemoteAction { + private final RemoteAction remoteAction; + private final RemoteActionFactory nextRemoteActionFactory; + private Boolean checkpoint; + private RemoteAction nextRemoteAction = null; + private Boolean isCancelled = false; + + public ComposableRemoteAction(RemoteAction remoteAction, + RemoteActionFactory nextRemoteActionFactory, + Boolean checkpoint) { + this.remoteAction = remoteAction; + this.nextRemoteActionFactory = nextRemoteActionFactory; + this.checkpoint = checkpoint; + } + + public static ComposableBuilder firstDo(RemoteAction remoteAction) { + return new ComposableBuilder<>(remoteAction); + } + + public ComposableRemoteAction then(RemoteActionFactory factory) { + return new ComposableRemoteAction<>(this, factory, false); + } + + public synchronized ComposableRemoteAction checkpoint() { + checkpoint = true; + return this; + } + + @Override + public U sync() throws PubNubException { + T result = remoteAction.sync(); + return nextRemoteActionFactory.create(result).sync(); + } + + @Override + public void async(@NotNull PNCallback callback) { + remoteAction.async( + (r, s) -> { + if (s.isError()) { + callback.onResponse(null, switchRetryReceiver(s)); + } else { + try { + synchronized (this) { + if (!isCancelled) { + RemoteAction newNextRemoteAction = nextRemoteActionFactory.create(r); + nextRemoteAction = newNextRemoteAction; + newNextRemoteAction.async((r2, s2) -> { + if (s2.isError()) { + callback.onResponse(null, switchRetryReceiver(s2)); + } else { + callback.onResponse(r2, switchRetryReceiver(s2)); + } + } + ); + } + } + } catch (PubNubException ex) { + callback.onResponse(null, PNStatus.builder() + .category(PNStatusCategory.PNBadRequestCategory) + .error(true) + .build()); + } + + } + } + ); + } + + private PNStatus switchRetryReceiver(PNStatus s) { + return s.toBuilder() + .executedEndpoint(this) + .build(); + } + + @Override + public synchronized void retry() { + if (checkpoint && nextRemoteAction != null) { + nextRemoteAction.retry(); + } else { + remoteAction.retry(); + } + } + + @Override + public synchronized void silentCancel() { + isCancelled = true; + remoteAction.silentCancel(); + if (nextRemoteAction != null) { + nextRemoteAction.silentCancel(); + } + } + + public static class ComposableBuilder { + + private final RemoteAction remoteAction; + + private boolean checkpoint; + + public ComposableBuilder(RemoteAction remoteAction) { + this.remoteAction = remoteAction; + } + + public ComposableRemoteAction then(RemoteActionFactory nextRemoteActionFactory) { + return new ComposableRemoteAction<>(remoteAction, nextRemoteActionFactory, checkpoint); + } + + public ComposableBuilder checkpoint() { + this.checkpoint = true; + return this; + } + + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/MappingRemoteAction.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/MappingRemoteAction.java new file mode 100644 index 000000000..821b6178f --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/MappingRemoteAction.java @@ -0,0 +1,44 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.models.consumer.PNStatus; +import org.jetbrains.annotations.NotNull; + +import java.net.HttpURLConnection; + +public class MappingRemoteAction implements RemoteAction { + private final T result; + private final PNFunction function; + private PNCallback cachedCallback; + + public static RemoteAction map(T result, PNFunction function) { + return new MappingRemoteAction<>(result, function); + } + + private MappingRemoteAction(T result, PNFunction function) { + this.result = result; + this.function = function; + } + + @Override + public U sync() throws PubNubException { + return function.invoke(result); + } + + @Override + public void async(@NotNull PNCallback callback) { + this.cachedCallback = callback; + callback.onResponse(function.invoke(result), PNStatus.builder().statusCode(HttpURLConnection.HTTP_OK).build()); + } + + @Override + public void retry() { + async(cachedCallback); + } + + @Override + public void silentCancel() { + + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction.java new file mode 100644 index 000000000..7988b3470 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction.java @@ -0,0 +1,6 @@ +package com.pubnub.api.endpoints.remoteaction; + +public interface PNFunction { + OUTPUT invoke(INPUT input); +} + diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction3.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction3.java new file mode 100644 index 000000000..89e5ea3a7 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/PNFunction3.java @@ -0,0 +1,5 @@ +package com.pubnub.api.endpoints.remoteaction; + +public interface PNFunction3 { + OUTPUT invoke(INPUT1 input1, INPUT2 input2, INPUT3 input3); +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteAction.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteAction.java new file mode 100644 index 000000000..81bc86cd6 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteAction.java @@ -0,0 +1,15 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import org.jetbrains.annotations.NotNull; + +public interface RemoteAction { + Output sync() throws PubNubException; + + void async(@NotNull PNCallback callback); + + void retry(); + + void silentCancel(); +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteActionFactory.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteActionFactory.java new file mode 100644 index 000000000..026787273 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/RemoteActionFactory.java @@ -0,0 +1,7 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; + +public interface RemoteActionFactory { + RemoteAction create(T input) throws PubNubException; +} diff --git a/src/main/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteAction.java b/src/main/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteAction.java new file mode 100644 index 000000000..e5a8cac92 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteAction.java @@ -0,0 +1,144 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import lombok.Data; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; + +public class RetryingRemoteAction implements RemoteAction { + + private final RemoteAction remoteAction; + private final int maxNumberOfAutomaticRetries; + private final PNOperationType operationType; + private final ExecutorService executorService; + private PNCallback cachedCallback; + + public RetryingRemoteAction(RemoteAction remoteAction, + int maxNumberOfAutomaticRetries, + PNOperationType operationType, + ExecutorService executorService) { + this.remoteAction = remoteAction; + this.maxNumberOfAutomaticRetries = maxNumberOfAutomaticRetries; + this.operationType = operationType; + this.executorService = executorService; + } + + public static RetryingRemoteAction autoRetry(RemoteAction remoteAction, + int maxNumberOfAutomaticRetries, + PNOperationType operationType, + ExecutorService executorService) { + return new RetryingRemoteAction<>(remoteAction, maxNumberOfAutomaticRetries, operationType, executorService); + } + + @Override + public T sync() throws PubNubException { + validate(); + PubNubException thrownException = null; + for (int i = 0; i < maxNumberOfAutomaticRetries; i++) { + try { + return remoteAction.sync(); + } catch (PubNubException ex) { + thrownException = ex; + } + } + //noinspection ConstantConditions + throw thrownException; + } + + @Override + public void async(@NotNull PNCallback callback) { + cachedCallback = callback; + executorService.execute(new Runnable() { + @Override + public void run() { + try { + validate(); + } catch (PubNubException ex) { + callback.onResponse(null, + PNStatus.builder() + .executedEndpoint(RetryingRemoteAction.this) + .operation(operationType) + .error(true) + .errorData(new PNErrorData(ex.getErrormsg(), ex)) + .build()); + return; + } + + ResultAndStatus lastResultAndStatus = null; + for (int i = 0; i < maxNumberOfAutomaticRetries; i++) { + lastResultAndStatus = syncAsync(); + if (!lastResultAndStatus.status.isError()) { + callback.onResponse(lastResultAndStatus.result, lastResultAndStatus.status); + return; + } + } + //noinspection ConstantConditions + callback.onResponse(lastResultAndStatus.result, lastResultAndStatus.status); + } + }); + } + + @Override + public void retry() { + async(cachedCallback); + } + + @Override + public void silentCancel() { + remoteAction.silentCancel(); + } + + @Data + private static class ResultAndStatus { + private final T result; + private final PNStatus status; + } + + + private ResultAndStatus syncAsync() { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference> atomicResultAndStatus = new AtomicReference<>(); + + remoteAction.async(new PNCallback() { + @Override + public void onResponse(@Nullable T result, @NotNull PNStatus status) { + atomicResultAndStatus.set(new ResultAndStatus<>(result, + status.toBuilder().executedEndpoint(RetryingRemoteAction.this).build())); + latch.countDown(); + } + }); + + try { + latch.await(); + return atomicResultAndStatus.get(); + } catch (InterruptedException e) { + remoteAction.silentCancel(); + return new ResultAndStatus<>(null, + PNStatus.builder() + .category(PNStatusCategory.PNUnknownCategory) + .operation(operationType) + .errorData(new PNErrorData(e.getMessage(), e)) + .error(true) + .executedEndpoint(this) + .build()); + } + } + + + private void validate() throws PubNubException { + if (maxNumberOfAutomaticRetries < 1) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS) + .errormsg("Number of retries cannot be less than 1").build(); + } + } +} diff --git a/src/main/java/com/pubnub/api/endpoints/vendor/AppEngineFactory.java b/src/main/java/com/pubnub/api/endpoints/vendor/AppEngineFactory.java new file mode 100644 index 000000000..4daae2128 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/vendor/AppEngineFactory.java @@ -0,0 +1,146 @@ +package com.pubnub.api.endpoints.vendor; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubUtil; +import lombok.extern.java.Log; +import okhttp3.*; +import okio.BufferedSink; +import okio.BufferedSource; +import okio.Okio; +import okio.Timeout; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +@Log +public class AppEngineFactory implements Call { + private Request request; + private PubNub pubNub; + + AppEngineFactory(Request request, PubNub pubNub) { + this.request = request; + this.pubNub = pubNub; + } + + @NotNull + @Override + public Request request() { + return request; + } + + @NotNull + @Override + public Response execute() throws IOException { + request = PubNubUtil.signRequest(request, pubNub.getConfiguration(), pubNub.getTimestamp()); + + URL url = request.url().url(); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setUseCaches(false); + connection.setDoOutput(true); + connection.setRequestMethod(request.method()); + + Headers headers = request.headers(); + if (headers != null) { + for (int i = 0; i < headers.size(); i++) { + String name = headers.name(i); + connection.setRequestProperty(name, headers.get(name)); + } + } + + if (request.body() != null) { + BufferedSink outbuf; + outbuf = Okio.buffer(Okio.sink(connection.getOutputStream())); + request.body().writeTo(outbuf); + outbuf.close(); + } + + connection.connect(); + + final BufferedSource source = Okio.buffer(Okio.source(connection.getInputStream())); + if (connection.getResponseCode() != 200) { + throw new IOException("Fail to call " + " :: " + source.readUtf8()); + } + Response response = new Response.Builder() + .code(connection.getResponseCode()) + .message(connection.getResponseMessage()) + .request(request) + .protocol(Protocol.HTTP_1_1) + .body(new ResponseBody() { + @Override + public MediaType contentType() { + return MediaType.parse(connection.getContentType()); + } + + @Override + public long contentLength() { + String contentLengthField = connection.getHeaderField("content-length"); + long contentLength; + try { + contentLength = Long.parseLong(contentLengthField); + } catch (NumberFormatException ignored) { + contentLength = -1; + } + return contentLength; + } + + @Override + public BufferedSource source() { + return source; + } + }) + .build(); + return response; + } + + @Override + public void enqueue(Callback responseCallback) { + + } + + @Override + public void cancel() { + + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public boolean isCanceled() { + return false; + } + + @NotNull + @Override + public Timeout timeout() { + return Timeout.NONE; + } + + @NotNull + @Override + public Call clone() { + try { + return (Call) super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + public static class Factory implements Call.Factory { + private PubNub pubNub; + + public Factory(PubNub pubNub) { + this.pubNub = pubNub; + } + + @NotNull + @Override + public Call newCall(Request request) { + return new AppEngineFactory(request, pubNub); + } + } +} diff --git a/src/main/java/com/pubnub/api/enums/PNHeartbeatNotificationOptions.java b/src/main/java/com/pubnub/api/enums/PNHeartbeatNotificationOptions.java new file mode 100644 index 000000000..abf74d8b9 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNHeartbeatNotificationOptions.java @@ -0,0 +1,10 @@ +package com.pubnub.api.enums; + + +public enum PNHeartbeatNotificationOptions { + + NONE, + FAILURES, + ALL + +} diff --git a/src/main/java/com/pubnub/api/enums/PNLogVerbosity.java b/src/main/java/com/pubnub/api/enums/PNLogVerbosity.java new file mode 100644 index 000000000..c81621f5d --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNLogVerbosity.java @@ -0,0 +1,8 @@ +package com.pubnub.api.enums; + +public enum PNLogVerbosity { + + NONE, + BODY, + +} diff --git a/src/main/java/com/pubnub/api/enums/PNMemberFields.java b/src/main/java/com/pubnub/api/enums/PNMemberFields.java new file mode 100644 index 000000000..ca836c75d --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNMemberFields.java @@ -0,0 +1,17 @@ +package com.pubnub.api.enums; + +public enum PNMemberFields { + CUSTOM("custom"), + USER("user"), + USER_CUSTOM("user.custom"); + + private final String value; + + PNMemberFields(String s) { + value = s; + } + + public String toString() { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/enums/PNMembershipFields.java b/src/main/java/com/pubnub/api/enums/PNMembershipFields.java new file mode 100644 index 000000000..3c39aae99 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNMembershipFields.java @@ -0,0 +1,17 @@ +package com.pubnub.api.enums; + +public enum PNMembershipFields { + CUSTOM("custom"), + SPACE("space"), + SPACE_CUSTOM("space.custom"); + + private final String value; + + PNMembershipFields(String s) { + value = s; + } + + public String toString() { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/enums/PNOperationType.java b/src/main/java/com/pubnub/api/enums/PNOperationType.java new file mode 100644 index 000000000..9a863ab99 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNOperationType.java @@ -0,0 +1,72 @@ +package com.pubnub.api.enums; + +/** + * Created by Max on 4/7/16. + */ +public enum PNOperationType { + PNSubscribeOperation, + PNUnsubscribeOperation, + + PNPublishOperation, + PNSignalOperation, + + PNHistoryOperation, + PNFetchMessagesOperation, + PNDeleteMessagesOperation, + PNMessageCountOperation, + + PNWhereNowOperation, + + PNHeartbeatOperation, + PNSetStateOperation, + PNAddChannelsToGroupOperation, + PNRemoveChannelsFromGroupOperation, + PNChannelGroupsOperation, + PNRemoveGroupOperation, + PNChannelsForGroupOperation, + PNPushNotificationEnabledChannelsOperation, + PNAddPushNotificationsOnChannelsOperation, + PNRemovePushNotificationsFromChannelsOperation, + PNRemoveAllPushNotificationsOperation, + PNTimeOperation, + + // CREATED + PNHereNowOperation, + PNGetState, + PNAccessManagerAudit, + PNAccessManagerGrant, + + // UUID Metadata + PNSetUuidMetadataOperation, + PNGetUuidMetadataOperation, + PNGetAllUuidMetadataOperation, + PNRemoveUuidMetadataOperation, + + // Channel Metadata + PNSetChannelMetadataOperation, + PNGetChannelMetadataOperation, + PNGetAllChannelsMetadataOperation, + PNRemoveChannelMetadataOperation, + + // Memberships + PNSetMembershipsOperation, + PNGetMembershipsOperation, + PNRemoveMembershipsOperation, + PNManageMembershipsOperation, + + // Members + PNSetChannelMembersOperation, + PNGetChannelMembersOperation, + PNRemoveChannelMembersOperation, + PNManageChannelMembersOperation, + + // PAMv3 + PNAccessManagerGrantToken, + + // Message Actions + PNAddMessageAction, + PNGetMessageActions, + PNDeleteMessageAction, + + PNFileAction +} diff --git a/src/main/java/com/pubnub/api/enums/PNPushEnvironment.java b/src/main/java/com/pubnub/api/enums/PNPushEnvironment.java new file mode 100644 index 000000000..d102daafd --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNPushEnvironment.java @@ -0,0 +1,7 @@ +package com.pubnub.api.enums; + +public enum PNPushEnvironment { + + DEVELOPMENT, + PRODUCTION +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/enums/PNPushType.java b/src/main/java/com/pubnub/api/enums/PNPushType.java new file mode 100644 index 000000000..3eecf1e6c --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNPushType.java @@ -0,0 +1,29 @@ +package com.pubnub.api.enums; + +public enum PNPushType { + + APNS("apns"), + + MPNS("mpns"), + + /** + * Use FCM instead + */ + @Deprecated + GCM("gcm"), + + FCM("gcm"), + + APNS2("apns2"); + + private final String value; + + PNPushType(String name) { + value = name; + } + + @Override + public String toString() { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/enums/PNReconnectionPolicy.java b/src/main/java/com/pubnub/api/enums/PNReconnectionPolicy.java new file mode 100644 index 000000000..55e6a9924 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNReconnectionPolicy.java @@ -0,0 +1,8 @@ +package com.pubnub.api.enums; + +public enum PNReconnectionPolicy { + + NONE, + LINEAR, + EXPONENTIAL +} diff --git a/src/main/java/com/pubnub/api/enums/PNSpaceFields.java b/src/main/java/com/pubnub/api/enums/PNSpaceFields.java new file mode 100644 index 000000000..925d0d097 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNSpaceFields.java @@ -0,0 +1,15 @@ +package com.pubnub.api.enums; + +public enum PNSpaceFields { + CUSTOM("custom"); + + private final String value; + + PNSpaceFields(String s) { + value = s; + } + + public String toString() { + return this.value; + } +} diff --git a/src/main/java/com/pubnub/api/enums/PNStatusCategory.java b/src/main/java/com/pubnub/api/enums/PNStatusCategory.java new file mode 100644 index 000000000..381914ef7 --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNStatusCategory.java @@ -0,0 +1,26 @@ +package com.pubnub.api.enums; + +public enum PNStatusCategory { + + PNUnknownCategory, + PNAcknowledgmentCategory, + PNAccessDeniedCategory, + PNTimeoutCategory, + PNNetworkIssuesCategory, + PNConnectedCategory, + PNReconnectedCategory, + PNDisconnectedCategory, + PNUnexpectedDisconnectCategory, + PNCancelledCategory, + PNBadRequestCategory, + PNURITooLongCategory, + PNMalformedFilterExpressionCategory, + PNMalformedResponseCategory, + PNDecryptionErrorCategory, + PNTLSConnectionFailedCategory, + PNTLSUntrustedCertificateCategory, + + PNRequestMessageCountExceededCategory, + PNReconnectionAttemptsExhaustedCategory, + PNRateLimitExceededCategory; +} diff --git a/src/main/java/com/pubnub/api/enums/PNUserFields.java b/src/main/java/com/pubnub/api/enums/PNUserFields.java new file mode 100644 index 000000000..e0fa65e2b --- /dev/null +++ b/src/main/java/com/pubnub/api/enums/PNUserFields.java @@ -0,0 +1,15 @@ +package com.pubnub.api.enums; + +public enum PNUserFields { + CUSTOM("custom"); + + private final String value; + + PNUserFields(String s) { + value = s; + } + + public String toString() { + return this.value; + } +} diff --git a/src/main/java/com/pubnub/api/interceptors/SignatureInterceptor.java b/src/main/java/com/pubnub/api/interceptors/SignatureInterceptor.java new file mode 100644 index 000000000..505934e84 --- /dev/null +++ b/src/main/java/com/pubnub/api/interceptors/SignatureInterceptor.java @@ -0,0 +1,26 @@ +package com.pubnub.api.interceptors; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubUtil; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + + +public class SignatureInterceptor implements Interceptor { + + private PubNub pubNub; + + public SignatureInterceptor(PubNub pubNubInstance) { + this.pubNub = pubNubInstance; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + Request request = PubNubUtil.signRequest(originalRequest, pubNub.getConfiguration(), pubNub.getTimestamp()); + return chain.proceed(request); + } +} diff --git a/src/main/java/com/pubnub/api/managers/BasePathManager.java b/src/main/java/com/pubnub/api/managers/BasePathManager.java new file mode 100644 index 000000000..31dd335c7 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/BasePathManager.java @@ -0,0 +1,75 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; + +/** + * A stateful manager to support base path construction, proxying and cache busting. + */ +public class BasePathManager { + + /** + * PubNub configuration storage. + */ + private PNConfiguration config; + /** + * for cache busting, the current subdomain number used. + */ + private int currentSubdomain; + + /** + * if using cache busting, this is the max number of subdomains that are supported. + */ + private static final int MAX_SUBDOMAIN = 20; + /** + * default subdomain used if cache busting is disabled. + */ + private static final String DEFAULT_SUBDOMAIN = "ps"; + /** + * default base path if a custom one is not provided. + */ + private static final String DEFAULT_BASE_PATH = "pndsn.com"; + + /** + * Initialize the path management. + * + * @param initialConfig configuration object + */ + public BasePathManager(PNConfiguration initialConfig) { + this.config = initialConfig; + currentSubdomain = 1; + } + + + /** + * Prepares a next usable base url. + * + * @return usable base url. + */ + public String getBasePath() { + StringBuilder constructedUrl = new StringBuilder("http"); + + if (config.isSecure()) { + constructedUrl.append("s"); + } + + constructedUrl.append("://"); + + if (config.getOrigin() != null) { + constructedUrl.append(config.getOrigin()); + } else if (config.isCacheBusting()) { + constructedUrl.append("ps").append(currentSubdomain).append(".").append(DEFAULT_BASE_PATH); + + if (currentSubdomain == MAX_SUBDOMAIN) { + currentSubdomain = 1; + } else { + currentSubdomain += 1; + } + + } else { + constructedUrl.append(DEFAULT_SUBDOMAIN).append(".").append(DEFAULT_BASE_PATH); + } + + return constructedUrl.toString(); + } + +} diff --git a/src/main/java/com/pubnub/api/managers/DelayedReconnectionManager.java b/src/main/java/com/pubnub/api/managers/DelayedReconnectionManager.java new file mode 100644 index 000000000..7a84b8599 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/DelayedReconnectionManager.java @@ -0,0 +1,68 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.ReconnectionCallback; +import com.pubnub.api.enums.PNReconnectionPolicy; +import lombok.extern.slf4j.Slf4j; + +import java.util.Timer; +import java.util.TimerTask; + +@Slf4j +public class DelayedReconnectionManager { + private static final int DELAY_SECONDS = 3; + private static final int MILLISECONDS = 1000; + + private final PNReconnectionPolicy pnReconnectionPolicy; + private ReconnectionCallback callback; + private PubNub pubnub; + + /** + * Timer for heartbeat operations. + */ + private Timer timer; + + public DelayedReconnectionManager(PubNub pubnub) { + this.pubnub = pubnub; + this.pnReconnectionPolicy = pubnub.getConfiguration().getReconnectionPolicy(); + } + + public void scheduleDelayedReconnection() { + stop(); + if (isReconnectionPolicyUndefined()) { + return; + } + + timer = new Timer("Delayed Reconnection Manager timer", true); + timer.schedule(new TimerTask() { + @Override + public void run() { + callTime(); + } + }, DELAY_SECONDS * MILLISECONDS); + } + + public void setReconnectionListener(ReconnectionCallback reconnectionCallback) { + this.callback = reconnectionCallback; + } + + void stop() { + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + private boolean isReconnectionPolicyUndefined() { + if (pnReconnectionPolicy == null || pnReconnectionPolicy == PNReconnectionPolicy.NONE) { + log.warn("reconnection policy is disabled, please handle reconnection manually."); + return true; + } + return false; + } + + private void callTime() { + stop(); + callback.onReconnection(); + } +} diff --git a/src/main/java/com/pubnub/api/managers/DuplicationManager.java b/src/main/java/com/pubnub/api/managers/DuplicationManager.java new file mode 100644 index 000000000..de16b513a --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/DuplicationManager.java @@ -0,0 +1,38 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.models.server.SubscribeMessage; + +import java.util.ArrayList; + +public class DuplicationManager { + + private ArrayList hashHistory; + private PNConfiguration pnConfiguration; + + public DuplicationManager(PNConfiguration pnc) { + this.hashHistory = new ArrayList<>(); + this.pnConfiguration = pnc; + } + + private String getKey(SubscribeMessage message) { + return message.getPublishMetaData().getPublishTimetoken().toString().concat("-").concat(Integer.toString(message.getPayload().hashCode())); + } + + public boolean isDuplicate(SubscribeMessage message) { + return hashHistory.contains(this.getKey(message)); + } + + public void addEntry(SubscribeMessage message) { + if (this.hashHistory.size() >= pnConfiguration.getMaximumMessagesCacheSize()) { + hashHistory.remove(0); + } + + hashHistory.add(this.getKey(message)); + } + + public void clearHistory() { + this.hashHistory.clear(); + } + +} diff --git a/src/main/java/com/pubnub/api/managers/ListenerManager.java b/src/main/java/com/pubnub/api/managers/ListenerManager.java new file mode 100644 index 000000000..0bc80dd56 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/ListenerManager.java @@ -0,0 +1,106 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; + +import java.util.ArrayList; +import java.util.List; + +public class ListenerManager { + + private final List listeners; + private final PubNub pubnub; + + public ListenerManager(PubNub pubnubInstance) { + this.listeners = new ArrayList<>(); + this.pubnub = pubnubInstance; + } + + public void addListener(SubscribeCallback listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public void removeListener(SubscribeCallback listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + private List getListeners() { + List tempCallbackList = new ArrayList<>(); + synchronized (listeners) { + tempCallbackList.addAll(listeners); + } + return tempCallbackList; + } + + /** + * announce a PNStatus to listeners. + * + * @param status PNStatus which will be broadcast to listeners. + */ + public void announce(PNStatus status) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.status(this.pubnub, status); + } + } + + public void announce(PNMessageResult message) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.message(this.pubnub, message); + } + } + + public void announce(PNPresenceEventResult presence) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.presence(this.pubnub, presence); + } + } + + public void announce(PNSignalResult signal) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.signal(this.pubnub, signal); + } + } + + public void announce(final PNUUIDMetadataResult uuidMetadataResult) { + for (final SubscribeCallback subscribeCallback: getListeners()) { + subscribeCallback.uuid(this.pubnub, uuidMetadataResult); + } + } + + public void announce(final PNChannelMetadataResult channelMetadataResult) { + for (final SubscribeCallback subscribeCallback: getListeners()) { + subscribeCallback.channel(this.pubnub, channelMetadataResult); + } + } + + public void announce(final PNMembershipResult membershipResult) { + for (final SubscribeCallback subscribeCallback: getListeners()) { + subscribeCallback.membership(this.pubnub, membershipResult); + } + } + + public void announce(PNMessageActionResult messageAction) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.messageAction(this.pubnub, messageAction); + } + } + + public void announce(PNFileEventResult fileEventResult) { + for (SubscribeCallback subscribeCallback : getListeners()) { + subscribeCallback.file(this.pubnub, fileEventResult); + } + } +} diff --git a/src/main/java/com/pubnub/api/managers/MapperManager.java b/src/main/java/com/pubnub/api/managers/MapperManager.java new file mode 100644 index 000000000..f6ae69d7e --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/MapperManager.java @@ -0,0 +1,275 @@ +package com.pubnub.api.managers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import retrofit2.Converter; +import retrofit2.converter.gson.GsonConverterFactory; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Iterator; +import java.util.Map; + +public class MapperManager { + + @Getter + private final Gson objectMapper; + @Getter + private final Converter.Factory converterFactory; + + private final ObjectMapper jacksonObjectMapper = new ObjectMapper(); + + public MapperManager() { + TypeAdapter booleanAsIntAdapter = getBooleanTypeAdapter(); + + this.objectMapper = new GsonBuilder() + .registerTypeAdapter(Boolean.class, booleanAsIntAdapter) + .registerTypeAdapter(boolean.class, booleanAsIntAdapter) + .registerTypeAdapter(JSONObject.class, new JSONObjectAdapter()) + .registerTypeAdapter(JSONArray.class, new JSONArrayAdapter()) + .create(); + this.converterFactory = GsonConverterFactory.create(this.getObjectMapper()); + } + + public boolean hasField(JsonElement element, String field) { + return element.getAsJsonObject().has(field); + } + + public JsonElement getField(JsonElement element, String field) { + return element.getAsJsonObject().get(field); + } + + public Iterator getArrayIterator(JsonElement element) { + return element.getAsJsonArray().iterator(); + } + + public Iterator getArrayIterator(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsJsonArray().iterator(); + } + + public Iterator> getObjectIterator(JsonElement element) { + return element.getAsJsonObject().entrySet().iterator(); + } + + public Iterator> getObjectIterator(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsJsonObject().entrySet().iterator(); + } + + public String elementToString(JsonElement element) { + return element.getAsString(); + } + + public String elementToString(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsString(); + } + + public int elementToInt(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsInt(); + } + + public boolean isJsonObject(JsonElement element) { + return element.isJsonObject(); + } + + public JsonObject getAsObject(JsonElement element) { + return element.getAsJsonObject(); + } + + public boolean getAsBoolean(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsBoolean(); + } + + public void putOnObject(JsonObject element, String key, JsonElement value) { + element.add(key, value); + } + + public JsonElement getArrayElement(JsonElement element, int index) { + return element.getAsJsonArray().get(index); + } + + public Long elementToLong(JsonElement element) { + return element.getAsLong(); + } + + public Long elementToLong(JsonElement element, String field) { + return element.getAsJsonObject().get(field).getAsLong(); + } + + public JsonArray getAsArray(JsonElement element) { + return element.getAsJsonArray(); + } + + @SuppressWarnings("unchecked") + public T fromJson(String input, Class clazz) throws PubNubException { + try { + return this.objectMapper.fromJson(input, clazz); + } catch (JsonParseException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_PARSING_ERROR) + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + } + + @SuppressWarnings("unchecked") + public T convertValue(JsonElement input, Class clazz) { + return (T) this.objectMapper.fromJson(input, clazz); + } + + @SuppressWarnings("unchecked") + public T convertValue(Object object, Class clazz) throws PubNubException { + return (T) fromJson(toJson(object), clazz); + } + + public String toJson(Object input) throws PubNubException { + try { + return this.objectMapper.toJson(input); + } catch (JsonParseException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_JSON_ERROR) + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + } + + public String toJsonUsinJackson(Object input) throws PubNubException { + try { + return this.jacksonObjectMapper.writeValueAsString(input); + } catch (JsonProcessingException e) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_JSON_ERROR) + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + } + + public void isValidJsonObject(Object object) throws PubNubException { + String json = toJson(object); + JsonElement jsonElement = new JsonParser().parse(json); + boolean isValid = isJsonObject(jsonElement); + if (!isValid) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_JSON).build(); + } + } + + @NotNull + private TypeAdapter getBooleanTypeAdapter() { + return new TypeAdapter() { + @Override + public void write(JsonWriter out, Boolean value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(value); + } + } + + @Override + public Boolean read(JsonReader in) throws IOException { + JsonToken peek = in.peek(); + switch (peek) { + case BOOLEAN: + return in.nextBoolean(); + //case NULL: + // in.nextNull(); + // return null; + case NUMBER: + return in.nextInt() != 0; + case STRING: + return Boolean.parseBoolean(in.nextString()); + default: + throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek); + } + } + }; + } + + private static class JSONObjectAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(JSONObject src, Type typeOfSrc, JsonSerializationContext context) { + if (src == null) { + return null; + } + JsonObject jsonObject = new JsonObject(); + Iterator keys = src.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = src.opt(key); + JsonElement jsonElement = context.serialize(value, value.getClass()); + jsonObject.add(key, jsonElement); + } + return jsonObject; + } + + @Override + public JSONObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json == null) { + return null; + } + try { + return new JSONObject(json.toString()); + } catch (JSONException e) { + e.printStackTrace(); + throw new JsonParseException(e); + } + } + } + + private static class JSONArrayAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(JSONArray src, Type typeOfSrc, JsonSerializationContext context) { + if (src == null) { + return null; + } + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < src.length(); i++) { + Object object = src.opt(i); + JsonElement jsonElement = context.serialize(object, object.getClass()); + jsonArray.add(jsonElement); + } + return jsonArray; + } + + @Override + public JSONArray deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json == null) { + return null; + } + try { + return new JSONArray(json.toString()); + } catch (JSONException e) { + e.printStackTrace(); + throw new JsonParseException(e); + } + } + } +} diff --git a/src/main/java/com/pubnub/api/managers/PublishSequenceManager.java b/src/main/java/com/pubnub/api/managers/PublishSequenceManager.java new file mode 100644 index 000000000..4498f6538 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/PublishSequenceManager.java @@ -0,0 +1,23 @@ +package com.pubnub.api.managers; + +public class PublishSequenceManager { + + + private int maxSequence; + private int nextSequence; + + public PublishSequenceManager(int providedMaxSequence) { + this.maxSequence = providedMaxSequence; + } + + public synchronized int getNextSequence() { + if (maxSequence == nextSequence) { + nextSequence = 1; + } else { + nextSequence += 1; + } + + return nextSequence; + } + +} diff --git a/src/main/java/com/pubnub/api/managers/ReconnectionManager.java b/src/main/java/com/pubnub/api/managers/ReconnectionManager.java new file mode 100644 index 000000000..7a1cc4858 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/ReconnectionManager.java @@ -0,0 +1,137 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNub; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.callbacks.ReconnectionCallback; +import com.pubnub.api.enums.PNReconnectionPolicy; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.PNTimeResult; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.Calendar; +import java.util.Timer; +import java.util.TimerTask; + + +@Slf4j +public class ReconnectionManager { + + private static final int LINEAR_INTERVAL = 3; + private static final int MIN_EXPONENTIAL_BACKOFF = 1; + private static final int MAX_EXPONENTIAL_BACKOFF = 32; + + private static final int MILLISECONDS = 1000; + + private ReconnectionCallback callback; + private PubNub pubnub; + + private int exponentialMultiplier = 1; + private int failedCalls = 0; + + private PNReconnectionPolicy pnReconnectionPolicy; + private int maxConnectionRetries; + + /** + * Timer for heartbeat operations. + */ + private Timer timer; + + public ReconnectionManager(PubNub pubnub) { + this.pubnub = pubnub; + this.pnReconnectionPolicy = pubnub.getConfiguration().getReconnectionPolicy(); + this.maxConnectionRetries = pubnub.getConfiguration().getMaximumReconnectionRetries(); + } + + public void setReconnectionListener(ReconnectionCallback reconnectionCallback) { + this.callback = reconnectionCallback; + } + + public void startPolling() { + if (isReconnectionPolicyUndefined()) { + return; + } + + exponentialMultiplier = 1; + failedCalls = 0; + + registerHeartbeatTimer(); + } + + private void registerHeartbeatTimer() { + // make sure only one timer is running at a time. + stopHeartbeatTimer(); + + if (isReconnectionPolicyUndefined()) { + return; + } + + if (maxConnectionRetries != -1 && failedCalls >= maxConnectionRetries) { // _what's -1? + callback.onMaxReconnectionExhaustion(); + return; + } + + timer = new Timer("Reconnection Manager timer", true); + + timer.schedule(new TimerTask() { + @Override + public void run() { + callTime(); + } + }, getNextInterval() * MILLISECONDS); + } + + int getNextInterval() { + int timerInterval = LINEAR_INTERVAL; + failedCalls++; + + if (pnReconnectionPolicy == PNReconnectionPolicy.EXPONENTIAL) { + exponentialMultiplier++; + timerInterval = (int) (Math.pow(2, exponentialMultiplier) - 1); + if (timerInterval > MAX_EXPONENTIAL_BACKOFF) { + timerInterval = MIN_EXPONENTIAL_BACKOFF; + exponentialMultiplier = 1; + log.debug("timerInterval > MAXEXPONENTIALBACKOFF at: " + Calendar.getInstance().getTime().toString()); + } else if (timerInterval < 1) { + timerInterval = MIN_EXPONENTIAL_BACKOFF; + } + log.debug("timerInterval = " + timerInterval + " at: " + Calendar.getInstance().getTime().toString()); + } + + if (pnReconnectionPolicy == PNReconnectionPolicy.LINEAR) { + timerInterval = LINEAR_INTERVAL; + } + + return timerInterval; + } + + private void stopHeartbeatTimer() { + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + private void callTime() { + pubnub.time().async(new PNCallback() { + @Override + public void onResponse(PNTimeResult result, @NotNull PNStatus status) { + if (!status.isError()) { + stopHeartbeatTimer(); + callback.onReconnection(); + } else { + log.debug("callTime() at: " + Calendar.getInstance().getTime().toString()); + registerHeartbeatTimer(); + } + } + }); + } + + private boolean isReconnectionPolicyUndefined() { + if (pnReconnectionPolicy == null || pnReconnectionPolicy == PNReconnectionPolicy.NONE) { + log.warn("reconnection policy is disabled, please handle reconnection manually."); + return true; + } + return false; + } +} diff --git a/src/main/java/com/pubnub/api/managers/RetrofitManager.java b/src/main/java/com/pubnub/api/managers/RetrofitManager.java new file mode 100644 index 000000000..32b8678ed --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/RetrofitManager.java @@ -0,0 +1,235 @@ +package com.pubnub.api.managers; + + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.endpoints.vendor.AppEngineFactory; +import com.pubnub.api.enums.PNLogVerbosity; +import com.pubnub.api.interceptors.SignatureInterceptor; +import com.pubnub.api.services.AccessManagerService; +import com.pubnub.api.services.ChannelGroupService; +import com.pubnub.api.services.ChannelMetadataService; +import com.pubnub.api.services.FilesService; +import com.pubnub.api.services.HistoryService; +import com.pubnub.api.services.MessageActionService; +import com.pubnub.api.services.PresenceService; +import com.pubnub.api.services.PublishService; +import com.pubnub.api.services.PushService; +import com.pubnub.api.services.S3Service; +import com.pubnub.api.services.SignalService; +import com.pubnub.api.services.SubscribeService; +import com.pubnub.api.services.TimeService; +import com.pubnub.api.services.UUIDMetadataService; +import lombok.Getter; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; + +import java.util.Collections; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public class RetrofitManager { + + private PubNub pubnub; + + private SignatureInterceptor signatureInterceptor; + + private OkHttpClient transactionClientInstance; + private OkHttpClient subscriptionClientInstance; + private OkHttpClient noSignatureClientInstance; + + + // services + @Getter + private PresenceService presenceService; + @Getter + private HistoryService historyService; + @Getter + private PushService pushService; + @Getter + private AccessManagerService accessManagerService; + @Getter + private ChannelGroupService channelGroupService; + @Getter + private TimeService timeService; + @Getter + private PublishService publishService; + @Getter + private SubscribeService subscribeService; + @Getter + private SignalService signalService; + @Getter + private UUIDMetadataService uuidMetadataService; + @Getter + private ChannelMetadataService channelMetadataService; + @Getter + private MessageActionService messageActionService; + @Getter + private final FilesService filesService; + + @Getter + private final S3Service s3Service; + @Getter + private final Retrofit transactionInstance; + @Getter + private final Retrofit subscriptionInstance; + @Getter + private final Retrofit noSignatureInstance; + + public RetrofitManager(PubNub pubNubInstance) { + this.pubnub = pubNubInstance; + + this.signatureInterceptor = new SignatureInterceptor(pubNubInstance); + + if (!pubNubInstance.getConfiguration().isGoogleAppEngineNetworking()) { + this.transactionClientInstance = createOkHttpClient( + prepareOkHttpClient( + this.pubnub.getConfiguration().getNonSubscribeRequestTimeout(), + this.pubnub.getConfiguration().getConnectTimeout() + ).addInterceptor(this.signatureInterceptor) + .retryOnConnectionFailure(false) + ); + + this.subscriptionClientInstance = createOkHttpClient( + prepareOkHttpClient( + this.pubnub.getConfiguration().getSubscribeTimeout(), + this.pubnub.getConfiguration().getConnectTimeout() + ).addInterceptor(this.signatureInterceptor) + .retryOnConnectionFailure(false) + ); + + this.noSignatureClientInstance = createOkHttpClient( + prepareOkHttpClient(this.pubnub.getConfiguration().getSubscribeTimeout(), + this.pubnub.getConfiguration().getConnectTimeout() + ).retryOnConnectionFailure(false) + ); + } + + this.transactionInstance = createRetrofit(this.transactionClientInstance); + this.subscriptionInstance = createRetrofit(this.subscriptionClientInstance); + this.noSignatureInstance = createRetrofit(this.noSignatureClientInstance); + + this.presenceService = transactionInstance.create(PresenceService.class); + this.historyService = transactionInstance.create(HistoryService.class); + this.pushService = transactionInstance.create(PushService.class); + this.accessManagerService = transactionInstance.create(AccessManagerService.class); + this.channelGroupService = transactionInstance.create(ChannelGroupService.class); + this.publishService = transactionInstance.create(PublishService.class); + this.subscribeService = subscriptionInstance.create(SubscribeService.class); + this.timeService = subscriptionInstance.create(TimeService.class); + this.signalService = transactionInstance.create(SignalService.class); + this.uuidMetadataService = transactionInstance.create(UUIDMetadataService.class); + this.channelMetadataService = transactionInstance.create(ChannelMetadataService.class); + this.messageActionService = transactionInstance.create(MessageActionService.class); + this.filesService = transactionInstance.create(FilesService.class); + this.s3Service = noSignatureInstance.create(S3Service.class); + } + + private OkHttpClient.Builder prepareOkHttpClient(int requestTimeout, int connectTimeOut) { + PNConfiguration pnConfiguration = pubnub.getConfiguration(); + OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); + httpClient.readTimeout(requestTimeout, TimeUnit.SECONDS); + httpClient.connectTimeout(connectTimeOut, TimeUnit.SECONDS); + + if (pubnub.getConfiguration().getLogVerbosity() == PNLogVerbosity.BODY) { + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + httpClient.addInterceptor(logging); + } + + if (pubnub.getConfiguration().getHttpLoggingInterceptor() != null) { + httpClient.addInterceptor(pubnub.getConfiguration().getHttpLoggingInterceptor()); + } + + if (pnConfiguration.getSslSocketFactory() != null && pnConfiguration.getX509ExtendedTrustManager() != null) { + httpClient.sslSocketFactory(pnConfiguration.getSslSocketFactory(), + pnConfiguration.getX509ExtendedTrustManager()); + } + + if (pnConfiguration.getConnectionSpec() != null) { + httpClient.connectionSpecs(Collections.singletonList(pnConfiguration.getConnectionSpec())); + } + + if (pnConfiguration.getHostnameVerifier() != null) { + httpClient.hostnameVerifier(pnConfiguration.getHostnameVerifier()); + } + + if (pubnub.getConfiguration().getProxy() != null) { + httpClient.proxy(pubnub.getConfiguration().getProxy()); + } + + if (pubnub.getConfiguration().getProxySelector() != null) { + httpClient.proxySelector(pubnub.getConfiguration().getProxySelector()); + } + + if (pubnub.getConfiguration().getProxyAuthenticator() != null) { + httpClient.proxyAuthenticator(pubnub.getConfiguration().getProxyAuthenticator()); + } + + if (pubnub.getConfiguration().getCertificatePinner() != null) { + httpClient.certificatePinner(pubnub.getConfiguration().getCertificatePinner()); + } + + + return httpClient; + } + + private OkHttpClient createOkHttpClient(OkHttpClient.Builder httpClient) { + OkHttpClient constructedClient = httpClient.build(); + + if (pubnub.getConfiguration().getMaximumConnections() != null) { + constructedClient.dispatcher().setMaxRequestsPerHost(pubnub.getConfiguration().getMaximumConnections()); + } + + return constructedClient; + } + + private Retrofit createRetrofit(OkHttpClient client) { + return createRetrofit(client, pubnub.getBaseUrl()); + } + + private Retrofit createRetrofit(OkHttpClient client, String baseUrl) { + Retrofit.Builder retrofitBuilder = new Retrofit.Builder(); + + if (pubnub.getConfiguration().isGoogleAppEngineNetworking()) { + retrofitBuilder.callFactory(new AppEngineFactory.Factory(pubnub)); + } + + retrofitBuilder = retrofitBuilder + .baseUrl(baseUrl) + .addConverterFactory(this.pubnub.getMapper().getConverterFactory()); + + if (!pubnub.getConfiguration().isGoogleAppEngineNetworking()) { + retrofitBuilder = retrofitBuilder.client(client); + } + + return retrofitBuilder.build(); + } + + + public ExecutorService getTransactionClientExecutorService() { + return transactionClientInstance.dispatcher().executorService(); + } + + private void closeExecutor(OkHttpClient client, boolean force) { + client.dispatcher().cancelAll(); + if (force) { + client.connectionPool().evictAll(); + ExecutorService executorService = client.dispatcher().executorService(); + executorService.shutdown(); + } + } + + public void destroy(boolean force) { + if (this.transactionClientInstance != null) { + closeExecutor(this.transactionClientInstance, force); + } + if (this.subscriptionClientInstance != null) { + closeExecutor(this.subscriptionClientInstance, force); + } + if (this.noSignatureClientInstance != null) { + closeExecutor(this.noSignatureClientInstance, force); + } + } +} diff --git a/src/main/java/com/pubnub/api/managers/StateManager.java b/src/main/java/com/pubnub/api/managers/StateManager.java new file mode 100644 index 000000000..90044e5ff --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/StateManager.java @@ -0,0 +1,484 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.builder.dto.ChangeTemporaryUnavailableOperation; +import com.pubnub.api.builder.dto.PresenceOperation; +import com.pubnub.api.builder.dto.PubSubOperation; +import com.pubnub.api.builder.dto.StateOperation; +import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.builder.dto.TimetokenAndRegionOperation; +import com.pubnub.api.builder.dto.UnsubscribeOperation; +import com.pubnub.api.models.SubscriptionItem; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class StateManager { + @Data + @AllArgsConstructor + private static class TemporaryUnavailableItem { + private String item; + private Date timestamp; + } + + static final int MILLIS_IN_SECOND = 1000; + + /** + * Contains a list of subscribed channels + */ + private final Map channels = new HashMap<>(); + /** + * Contains a list of subscribed presence channels. + */ + private final Map presenceChannels = new HashMap<>(); + + /** + * Contains a list of subscribed channel groups. + */ + private final Map groups = new HashMap<>(); + + /** + * Contains a list of subscribed presence channel groups. + */ + private final Map presenceGroups = new HashMap<>(); + + private final Map heartbeatChannels = new HashMap<>(); + private final Map heartbeatGroups = new HashMap<>(); + + private final List temporaryUnavailableChannels = new ArrayList<>(); + private final List temporaryUnavailableChannelGroups = new ArrayList<>(); + + /** + * Store the latest timetoken to subscribe with, null by default to get the latest timetoken. + */ + private Long timetoken = 0L; + private Long storedTimetoken = null; // when changing the channel mix, store the timetoken for a later date. + + /** + * Keep track of Region to support PSV2 specification. + */ + private String region = null; + + private final PNConfiguration configuration; + private boolean shouldAnnounce = false; + + public StateManager(final PNConfiguration configuration) { + this.configuration = configuration; + } + + public synchronized boolean handleOperation(final PubSubOperation... pubSubOperations) { + boolean stateChanged = false; + for (PubSubOperation pubSubOperation : pubSubOperations) { + if (pubSubOperation instanceof SubscribeOperation) { + if (adaptSubscribeBuilder((SubscribeOperation) pubSubOperation)) { + stateChanged = true; + shouldAnnounce = true; + } + } else if (pubSubOperation instanceof UnsubscribeOperation) { + unsubscribe((UnsubscribeOperation) pubSubOperation); + stateChanged = true; + shouldAnnounce = true; + } else if (pubSubOperation instanceof StateOperation) { + adaptStateBuilder((StateOperation) pubSubOperation); + } else if (pubSubOperation instanceof PresenceOperation) { + adaptPresenceBuilder((PresenceOperation) pubSubOperation); + } else if (pubSubOperation instanceof TimetokenAndRegionOperation) { + TimetokenAndRegionOperation ttAndReg = (TimetokenAndRegionOperation) pubSubOperation; + updateTimetokenAndRegion(ttAndReg.getTimetoken(), ttAndReg.getRegion()); + stateChanged = true; + } else if (pubSubOperation instanceof PubSubOperation.DisconnectOperation) { + resetTemporaryUnavailableChannelsAndGroups(); + } else if (pubSubOperation instanceof ChangeTemporaryUnavailableOperation) { + changeTemporary((ChangeTemporaryUnavailableOperation) pubSubOperation); + } else if (pubSubOperation instanceof PubSubOperation.ConnectedStatusAnnouncedOperation) { + shouldAnnounce = false; + } + } + return stateChanged; + } + + public synchronized SubscriptionStateData subscriptionStateData(Boolean includePresence) { + return subscriptionStateData(includePresence, ChannelFilter.WITH_TEMPORARY_UNAVAILABLE); + } + + public synchronized SubscriptionStateData subscriptionStateData(Boolean includePresence, + ChannelFilter channelFilter) { + final List channelsList; + final List groupsList; + if (channelFilter == ChannelFilter.WITH_TEMPORARY_UNAVAILABLE) { + channelsList = prepareMembershipList(channels, presenceChannels, includePresence); + groupsList = prepareMembershipList(groups, presenceGroups, includePresence); + } else { + channelsList = effectiveChannels(includePresence); + groupsList = effectiveChannelGroups(includePresence); + } + return new SubscriptionStateData( + createStatePayload(), + groupsList, + channelsList, + timetoken, + region, + hasAnythingToSubscribe(), + subscribedToOnlyTemporaryUnavailable(), + shouldAnnounce + ); + } + + public synchronized HeartbeatStateData heartbeatStateData() { + //noinspection deprecation + if (configuration.isManagePresenceListManually()) { + return new HeartbeatStateData(createHeartbeatStatePayload(), + getNames(heartbeatGroups), + getNames(heartbeatChannels)); + } else { + List heartbeatGroupNames = getNames(heartbeatGroups); + heartbeatGroupNames.addAll(getNames(groups)); + List heartbeatChannelNames = getNames(heartbeatChannels); + heartbeatChannelNames.addAll(getNames(channels)); + return new HeartbeatStateData(Collections.emptyMap(), + heartbeatGroupNames, + heartbeatChannelNames); + } + } + + private void updateTimetokenAndRegion(final Long newTimetoken, final String region) { + if (storedTimetoken != null) { + timetoken = storedTimetoken; + storedTimetoken = null; + } else { + timetoken = newTimetoken; + } + + this.region = region; + } + + private void explicitlySetTimetoken(final Long timetokenToSet) { + if (timetokenToSet != null) { + this.timetoken = timetokenToSet; + } + + // if the timetoken is not at starting position, reset the timetoken to get a connected event + // and store the old timetoken to be reused later during subscribe. + if (timetoken != 0L) { + storedTimetoken = timetoken; + } + timetoken = 0L; + } + + private boolean adaptSubscribeBuilder(SubscribeOperation subscribeOperation) { + boolean changeDetected = false; + + for (String channel : subscribeOperation.getChannels()) { + if (channel == null || channel.length() == 0) { + continue; + } + + final SubscriptionItem subscriptionItem = new SubscriptionItem().setName(channel); + changeDetected = putIfDifferent(channels, channel, subscriptionItem) || changeDetected; + + if (subscribeOperation.isPresenceEnabled()) { + final SubscriptionItem presenceSubscriptionItem = new SubscriptionItem().setName(channel); + changeDetected = putIfDifferent(presenceChannels, channel, presenceSubscriptionItem) || changeDetected; + } + } + + for (String channelGroup : subscribeOperation.getChannelGroups()) { + if (channelGroup == null || channelGroup.length() == 0) { + continue; + } + + final SubscriptionItem subscriptionItem = new SubscriptionItem().setName(channelGroup); + changeDetected = putIfDifferent(groups, channelGroup, subscriptionItem) || changeDetected; + + if (subscribeOperation.isPresenceEnabled()) { + final SubscriptionItem presenceSubscriptionItem = new SubscriptionItem().setName(channelGroup); + changeDetected = putIfDifferent(presenceGroups, + channelGroup, + presenceSubscriptionItem) || changeDetected; + } + + } + if (changeDetected) { + explicitlySetTimetoken(subscribeOperation.getTimetoken()); + } + return changeDetected; + } + + private boolean putIfDifferent(final Map map, final String key, final T newValue) { + final T existingValue = map.get(key); + if (existingValue == null) { + map.put(key, newValue); + return true; + } else { + if (existingValue.equals(newValue)) { + return false; + } else { + map.put(key, newValue); + return true; + } + } + } + + private void adaptStateBuilder(StateOperation stateOperation) { + for (String channel : stateOperation.getChannels()) { + SubscriptionItem subscribedChannel = channels.get(channel); + + if (subscribedChannel != null) { + subscribedChannel.setState(stateOperation.getState()); + } + + SubscriptionItem heartbeatChannel = heartbeatChannels.get(channel); + + if (heartbeatChannel != null) { + heartbeatChannel.setState(stateOperation.getState()); + } + } + + for (String channelGroup : stateOperation.getChannelGroups()) { + SubscriptionItem subscribedChannelGroup = groups.get(channelGroup); + + if (subscribedChannelGroup != null) { + subscribedChannelGroup.setState(stateOperation.getState()); + } + + SubscriptionItem heartbeatChannelGroup = heartbeatGroups.get(channelGroup); + + if (heartbeatChannelGroup != null) { + heartbeatChannelGroup.setState(stateOperation.getState()); + } + } + } + + + private void unsubscribe(UnsubscribeOperation unsubscribeOperation) { + for (String channel : unsubscribeOperation.getChannels()) { + this.channels.remove(channel); + this.presenceChannels.remove(channel); + } + removeTemporaryUnavailableChannels(unsubscribeOperation.getChannels()); + + for (String channelGroup : unsubscribeOperation.getChannelGroups()) { + this.groups.remove(channelGroup); + this.presenceGroups.remove(channelGroup); + } + removeTemporaryUnavailableChannelGroups(unsubscribeOperation.getChannelGroups()); + + // if we unsubscribed from all the channels, reset the timetoken back to zero and remove the region. + if (this.isEmpty()) { + region = null; + storedTimetoken = null; + } else { + storedTimetoken = timetoken; + } + timetoken = 0L; + } + + private void adaptPresenceBuilder(PresenceOperation presenceOperation) { + for (String channel : presenceOperation.getChannels()) { + if (channel == null || channel.length() == 0) { + continue; + } + + if (presenceOperation.isConnected()) { + SubscriptionItem subscriptionItem = new SubscriptionItem().setName(channel); + heartbeatChannels.put(channel, subscriptionItem); + } else { + heartbeatChannels.remove(channel); + } + + } + + for (String channelGroup : presenceOperation.getChannelGroups()) { + if (channelGroup == null || channelGroup.length() == 0) { + continue; + } + + if (presenceOperation.isConnected()) { + SubscriptionItem subscriptionItem = new SubscriptionItem().setName(channelGroup); + heartbeatGroups.put(channelGroup, subscriptionItem); + } else { + heartbeatGroups.remove(channelGroup); + } + + } + } + + private void changeTemporary(ChangeTemporaryUnavailableOperation operation) { + for (String channel : operation.getUnavailableChannels()) { + temporaryUnavailableChannels.add(new TemporaryUnavailableItem(channel, new Date())); + } + for (String channelGroup : operation.getUnavailableChannelGroups()) { + temporaryUnavailableChannelGroups.add(new TemporaryUnavailableItem(channelGroup, new Date())); + } + + removeTemporaryUnavailableChannels(operation.getAvailableChannels()); + removeTemporaryUnavailableChannelGroups(operation.getAvailableChannelGroups()); + } + + private Map createStatePayload() { + return createStatePayload(channels, groups); + } + + private Map createHeartbeatStatePayload() { + return createStatePayload(heartbeatChannels, heartbeatGroups); + } + + private Map createStatePayload(Map channels, Map groups) { + Map stateResponse = new HashMap<>(); + + for (SubscriptionItem channel : channels.values()) { + if (channel.getState() != null) { + stateResponse.put(channel.getName(), channel.getState()); + } + } + + for (SubscriptionItem channelGroup : groups.values()) { + if (channelGroup.getState() != null) { + stateResponse.put(channelGroup.getName(), channelGroup.getState()); + } + } + + return stateResponse; + } + + private boolean hasAnythingToSubscribe() { + final List combinedChannels = prepareMembershipList(channels, presenceChannels, true); + final List combinedChannelGroups = prepareMembershipList(groups, presenceGroups, true); + + return !combinedChannels.isEmpty() || !combinedChannelGroups.isEmpty(); + } + + private void resetTemporaryUnavailableChannelsAndGroups() { + temporaryUnavailableChannels.clear(); + temporaryUnavailableChannelGroups.clear(); + } + + private void removeTemporaryUnavailableChannels(Collection channels) { + removeTemporaryUnavailable(channels, temporaryUnavailableChannels); + } + + private void removeTemporaryUnavailableChannelGroups(Collection channelGroups) { + removeTemporaryUnavailable(channelGroups, temporaryUnavailableChannelGroups); + } + + private void removeTemporaryUnavailable(final Collection toBeRemoved, + final Collection temporaryUnavailableItems) { + if (toBeRemoved.isEmpty()) { + return; + } + final List temporaryUnavailableItemsToBeRemoved = new ArrayList<>(); + for (final TemporaryUnavailableItem temporaryUnavailableItem : temporaryUnavailableItems) { + if (toBeRemoved.contains(temporaryUnavailableItem.getItem())) { + temporaryUnavailableItemsToBeRemoved.add(temporaryUnavailableItem); + } + } + temporaryUnavailableItems.removeAll(temporaryUnavailableItemsToBeRemoved); + } + + private boolean subscribedToOnlyTemporaryUnavailable() { + return effectiveChannels().isEmpty() && effectiveChannelGroups().isEmpty(); + } + + private List effectiveChannels() { + return effectiveChannels(true); + } + + private List effectiveChannels(boolean includePresence) { + final List effectiveChannelsList = prepareMembershipList(channels, presenceChannels, includePresence); + effectiveChannelsList.removeAll(channelsToPostponeSubscription(temporaryUnavailableChannels)); + return effectiveChannelsList; + } + + private List effectiveChannelGroups() { + return effectiveChannelGroups(true); + } + + private List effectiveChannelGroups(boolean includePresence) { + final List effectiveChannelGroupsList = prepareMembershipList(groups, presenceGroups, includePresence); + effectiveChannelGroupsList.removeAll(channelGroupsToPostponeSubscription(temporaryUnavailableChannelGroups)); + return effectiveChannelGroupsList; + } + + private List channelsToPostponeSubscription(final List temporaryUnavailableChannels) { + final List result = new ArrayList<>(); + + for (TemporaryUnavailableItem temporaryUnavailableChannel : temporaryUnavailableChannels) { + if (temporaryUnavailableChannel.getTimestamp() + .after(new Date(System.currentTimeMillis() - configuration.getConnectTimeout() * MILLIS_IN_SECOND))) { + result.add(temporaryUnavailableChannel.getItem()); + } + } + return result; + } + + private List channelGroupsToPostponeSubscription(final List temporaryUnavailableChannelGroups) { + final List result = new ArrayList<>(); + + for (TemporaryUnavailableItem temporaryUnavailableChannelGroup : temporaryUnavailableChannelGroups) { + if (temporaryUnavailableChannelGroup.getTimestamp() + .after(new Date(System.currentTimeMillis() - configuration.getConnectTimeout() * MILLIS_IN_SECOND))) { + result.add(temporaryUnavailableChannelGroup.getItem()); + } + } + return result; + } + + private boolean isEmpty() { + return (channels.isEmpty() && presenceChannels.isEmpty() && groups.isEmpty() && presenceGroups.isEmpty()); + } + + + private List getNames(Map dataStorage) { + return new ArrayList<>(dataStorage.keySet()); + } + + private void addPresence(List response, Map presenceStorage) { + for (SubscriptionItem presenceChannelGroupItem : presenceStorage.values()) { + response.add(presenceChannelGroupItem.getName().concat("-pnpres")); + } + } + + private List prepareMembershipList(Map dataStorage, Map presenceStorage, boolean includePresence) { + List response = getNames(dataStorage); + + if (includePresence) { + addPresence(response, presenceStorage); + } + + return response; + } + + enum ChannelFilter { + WITH_TEMPORARY_UNAVAILABLE, + WITHOUT_TEMPORARY_UNAVAILABLE + + } + + @Data + public static class SubscriptionStateData { + private final Map statePayload; + private final List channelGroups; + private final List channels; + private final Long timetoken; + private final String region; + private final boolean anythingToSubscribe; + private final boolean subscribedToOnlyTemporaryUnavailable; + private final boolean shouldAnnounce; + } + + @Data + public static class HeartbeatStateData { + private final Map statePayload; + private final List heartbeatChannelGroups; + private final List heartbeatChannels; + } +} diff --git a/src/main/java/com/pubnub/api/managers/SubscriptionManager.java b/src/main/java/com/pubnub/api/managers/SubscriptionManager.java new file mode 100644 index 000000000..b69576e7c --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/SubscriptionManager.java @@ -0,0 +1,498 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNub; +import com.pubnub.api.builder.dto.ChangeTemporaryUnavailableOperation; +import com.pubnub.api.builder.dto.ChangeTemporaryUnavailableOperation.ChangeTemporaryUnavailableOperationBuilder; +import com.pubnub.api.builder.dto.PresenceOperation; +import com.pubnub.api.builder.dto.PubSubOperation; +import com.pubnub.api.builder.dto.StateOperation; +import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.builder.dto.TimetokenAndRegionOperation; +import com.pubnub.api.builder.dto.UnsubscribeOperation; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.callbacks.ReconnectionCallback; +import com.pubnub.api.endpoints.presence.Heartbeat; +import com.pubnub.api.endpoints.presence.Leave; +import com.pubnub.api.endpoints.pubsub.Subscribe; +import com.pubnub.api.enums.PNHeartbeatNotificationOptions; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.server.SubscribeMessage; +import com.pubnub.api.workers.SubscribeMessageWorker; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.LinkedBlockingQueue; + +import static com.pubnub.api.managers.StateManager.ChannelFilter.WITHOUT_TEMPORARY_UNAVAILABLE; +import static com.pubnub.api.managers.StateManager.MILLIS_IN_SECOND; + +@Slf4j +public class SubscriptionManager { + private static final int TWO_SECONDS = 2 * MILLIS_IN_SECOND; + + private static final int HEARTBEAT_INTERVAL_MULTIPLIER = 1000; + + private volatile boolean connected; + private volatile boolean httpRequestPending = false; + + PubNub pubnub; + private final TelemetryManager telemetryManager; + private final TokenManager tokenManager; + private Subscribe subscribeCall; + private Heartbeat heartbeatCall; + + private final LinkedBlockingQueue messageQueue; + + private final DuplicationManager duplicationManager; + + /** + * Timer for heartbeat operations. + */ + private Timer timer; + + final StateManager subscriptionState; + + private final ListenerManager listenerManager; + private final ReconnectionManager reconnectionManager; + private final DelayedReconnectionManager delayedReconnectionManager; + private final RetrofitManager retrofitManager; + + private Timer temporaryUnavailableChannelsDelayer; + + private Thread consumerThread; + + public SubscriptionManager(final PubNub pubnubInstance, + final RetrofitManager retrofitManagerInstance, + final TelemetryManager telemetry, + final StateManager stateManager, + final ListenerManager listenerManager, + final ReconnectionManager reconnectionManager, + final DelayedReconnectionManager delayedReconnectionManager, + final DuplicationManager duplicationManager, + final TokenManager tokenManager) { + this.pubnub = pubnubInstance; + this.telemetryManager = telemetry; + + this.messageQueue = new LinkedBlockingQueue<>(); + this.subscriptionState = stateManager; + + this.listenerManager = listenerManager; + this.reconnectionManager = reconnectionManager; + this.delayedReconnectionManager = delayedReconnectionManager; + this.retrofitManager = retrofitManagerInstance; + this.duplicationManager = duplicationManager; + this.tokenManager = tokenManager; + + + final ReconnectionCallback reconnectionCallback = new ReconnectionCallback() { + @Override + public void onReconnection() { + reconnect(PubSubOperation.NO_OP); + StateManager.SubscriptionStateData subscriptionStateData = subscriptionState.subscriptionStateData(true); + PNStatus pnStatus = PNStatus.builder() + .error(false) + .affectedChannels(subscriptionStateData.getChannels()) + .affectedChannelGroups(subscriptionStateData.getChannelGroups()) + .category(PNStatusCategory.PNReconnectedCategory) + .build(); + + listenerManager.announce(pnStatus); + } + + @Override + public void onMaxReconnectionExhaustion() { + StateManager.SubscriptionStateData subscriptionStateData = subscriptionState.subscriptionStateData(true); + PNStatus pnStatus = PNStatus.builder() + .error(false) + .category(PNStatusCategory.PNReconnectionAttemptsExhaustedCategory) + .affectedChannels(subscriptionStateData.getChannels()) + .affectedChannelGroups(subscriptionStateData.getChannelGroups()) + .build(); + listenerManager.announce(pnStatus); + + disconnect(); + + } + }; + + this.delayedReconnectionManager.setReconnectionListener(reconnectionCallback); + this.reconnectionManager.setReconnectionListener(reconnectionCallback); + + if (this.pubnub.getConfiguration().isStartSubscriberThread()) { + consumerThread = new Thread(new SubscribeMessageWorker( + this.pubnub, listenerManager, messageQueue, duplicationManager)); + consumerThread.setName("Subscription Manager Consumer Thread"); + consumerThread.setDaemon(true); + consumerThread.start(); + } + } + + public void reconnect() { + reconnect(PubSubOperation.NO_OP); + } + + private void reconnect(PubSubOperation pubSubOperation) { + connected = true; + this.startSubscribeLoop(pubSubOperation); + this.registerHeartbeatTimer(PubSubOperation.NO_OP); + } + + public synchronized void disconnect() { + connected = false; + cancelDelayedLoopIterationForTemporaryUnavailableChannels(); + subscriptionState.handleOperation(PubSubOperation.DISCONNECT); + delayedReconnectionManager.stop(); + stopHeartbeatTimer(); + stopSubscribeLoop(); + } + + + @Deprecated + public synchronized void stop() { + this.disconnect(); + consumerThread.interrupt(); + } + + public synchronized void destroy(boolean forceDestroy) { + this.disconnect(); + if (forceDestroy && consumerThread != null) { + consumerThread.interrupt(); + } + } + + public void adaptStateBuilder(StateOperation stateOperation) { + reconnect(stateOperation); + } + + public void adaptSubscribeBuilder(SubscribeOperation subscribeOperation) { + reconnect(subscribeOperation); + } + + public void adaptPresenceBuilder(PresenceOperation presenceOperation) { + if (!this.pubnub.getConfiguration().isSuppressLeaveEvents() && !presenceOperation.isConnected()) { + new Leave(pubnub, this.telemetryManager, this.retrofitManager, tokenManager) + .channels(presenceOperation.getChannels()).channelGroups(presenceOperation.getChannelGroups()) + .async(new PNCallback() { + @Override + public void onResponse(Boolean result, @NotNull PNStatus status) { + listenerManager.announce(status); + } + }); + } + + registerHeartbeatTimer(presenceOperation); + } + + public void adaptUnsubscribeBuilder(UnsubscribeOperation unsubscribeOperation) { + if (!this.pubnub.getConfiguration().isSuppressLeaveEvents()) { + new Leave(pubnub, this.telemetryManager, this.retrofitManager, tokenManager) + .channels(unsubscribeOperation.getChannels()) + .channelGroups(unsubscribeOperation.getChannelGroups()) + .async(new PNCallback() { + @Override + public void onResponse(Boolean result, @NotNull PNStatus status) { + //In case we get PNAccessDeniedCategory while sending Leave event we do not announce it. + //Client did initiate it explicitly, + if (status.isError() && status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + return; + } + listenerManager.announce(status); + } + }); + } + + reconnect(unsubscribeOperation); + } + + private synchronized void registerHeartbeatTimer(PubSubOperation pubSubOperation) { + // make sure only one timer is running at a time. + stopHeartbeatTimer(); + + // if the interval is 0 or less, do not start the timer + if (pubnub.getConfiguration().getHeartbeatInterval() <= 0) { + return; + } + + timer = new Timer("Subscription Manager Heartbeat Timer", true); + timer.schedule(new TimerTask() { + @Override + public void run() { + performHeartbeatLoop(pubSubOperation); + } + }, 0, pubnub.getConfiguration().getHeartbeatInterval() * HEARTBEAT_INTERVAL_MULTIPLIER); + + } + + private void stopHeartbeatTimer() { + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + private synchronized void cancelDelayedLoopIterationForTemporaryUnavailableChannels() { + if (temporaryUnavailableChannelsDelayer != null) { + temporaryUnavailableChannelsDelayer.cancel(); + temporaryUnavailableChannelsDelayer = null; + } + } + + private void scheduleDelayedLoopIterationForTemporaryUnavailableChannels() { + cancelDelayedLoopIterationForTemporaryUnavailableChannels(); + + temporaryUnavailableChannelsDelayer = new Timer("Subscription Manager TMP Unavailable Channel Delayer", true); + temporaryUnavailableChannelsDelayer.schedule(new TimerTask() { + @Override + public void run() { + startSubscribeLoop(PubSubOperation.NO_OP); + } + }, TWO_SECONDS); + } + + /** + * user is calling subscribe: + * + * if the state has changed we should restart the subscribe loop + * if the state hasn't change but the loop is not running we should restart the loop + * if the state hasn't change and the loop is running fine, we should do nothing + * + */ + + synchronized void startSubscribeLoop(final PubSubOperation... pubSubOperations) { + if (!connected) { + return; + } + boolean subscriptionLoopStateChanged = subscriptionState.handleOperation(pubSubOperations); + if (!subscriptionLoopStateChanged && httpRequestPending) { + return; + } + + stopSubscribeLoop(); + + for (PubSubOperation pubSubOperation : pubSubOperations) { + if (pubSubOperation instanceof SubscribeOperation) { + duplicationManager.clearHistory(); + } + } + + final StateManager.SubscriptionStateData subscriptionStateData = subscriptionState.subscriptionStateData( + true, + WITHOUT_TEMPORARY_UNAVAILABLE); + + if (!subscriptionStateData.isAnythingToSubscribe()) { + return; + } + + if (subscriptionStateData.isSubscribedToOnlyTemporaryUnavailable()) { + scheduleDelayedLoopIterationForTemporaryUnavailableChannels(); + return; + } + + httpRequestPending = true; + subscribeCall = new Subscribe(pubnub, this.retrofitManager, tokenManager) + .channels(subscriptionStateData.getChannels()) + .channelGroups(subscriptionStateData.getChannelGroups()) + .timetoken(subscriptionStateData.getTimetoken()) + .region(subscriptionStateData.getRegion()) + .filterExpression(pubnub.getConfiguration().getFilterExpression()) + .state(subscriptionStateData.getStatePayload()); + + subscribeCall.async((result, status) -> { + httpRequestPending = false; + if (status.isError()) { + handleError(status, pubSubOperations); + } else { + final ChangeTemporaryUnavailableOperationBuilder availableChannels = ChangeTemporaryUnavailableOperation + .builder(); + if (status.getCategory() == PNStatusCategory.PNAcknowledgmentCategory) { + final List affectedChannels = status.getAffectedChannels(); + final List affectedChannelGroups = status.getAffectedChannelGroups(); + + if (affectedChannels != null) { + for (final String affectedChannel : affectedChannels) { + availableChannels.availableChannel(affectedChannel); + } + } + if (affectedChannelGroups != null) { + for (final String affectedChannelGroup : affectedChannelGroups) { + availableChannels.availableChannelGroup(affectedChannelGroup); + } + } + } + + final PubSubOperation statusAnnouncedOperation; + if (subscriptionStateData.isShouldAnnounce()) { + PNStatus pnStatus = createPublicStatus(status) + .category(PNStatusCategory.PNConnectedCategory) + .error(false) + .build(); + listenerManager.announce(pnStatus); + statusAnnouncedOperation = PubSubOperation.STATUS_ANNOUNCED; + } else { + statusAnnouncedOperation = PubSubOperation.NO_OP; + } + + Integer requestMessageCountThreshold = pubnub.getConfiguration().getRequestMessageCountThreshold(); + if (requestMessageCountThreshold != null && requestMessageCountThreshold <= result.getMessages() + .size()) { + PNStatus pnStatus = createPublicStatus(status) + .category(PNStatusCategory.PNRequestMessageCountExceededCategory) + .error(false) + .build(); + + listenerManager.announce(pnStatus); + } + + if (result.getMessages().size() != 0) { + messageQueue.addAll(result.getMessages()); + } + + final TimetokenAndRegionOperation timetokenAndRegionOperation = new TimetokenAndRegionOperation( + result.getMetadata() + .getTimetoken(), + result.getMetadata().getRegion()); + startSubscribeLoop(timetokenAndRegionOperation, availableChannels.build(), statusAnnouncedOperation); + } + }); + + } + + private void handleError(@NotNull PNStatus status, + PubSubOperation... pubSubOperations) { + final PNStatusCategory category = status.getCategory(); + + switch (category) { + case PNTimeoutCategory: + startSubscribeLoop(pubSubOperations); + break; + case PNUnexpectedDisconnectCategory: + // stop all announcements and ask the reconnection manager to start polling for connection + // restoration.. + disconnect(); + listenerManager.announce(status); + reconnectionManager.startPolling(); + break; + case PNBadRequestCategory: + case PNURITooLongCategory: + disconnect(); + listenerManager.announce(status); + break; + case PNAccessDeniedCategory: + listenerManager.announce(status); + final List affectedChannels = status.getAffectedChannels(); + final List affectedChannelGroups = status.getAffectedChannelGroups(); + final ChangeTemporaryUnavailableOperationBuilder unavailableChannels = ChangeTemporaryUnavailableOperation + .builder(); + if (affectedChannels != null || affectedChannelGroups != null) { + if (affectedChannels != null) { + for (final String channelToMoveToTemporaryUnavailable : affectedChannels) { + unavailableChannels.unavailableChannel(channelToMoveToTemporaryUnavailable); + } + } + if (affectedChannelGroups != null) { + for (final String channelGroupToMoveToTemporaryUnavailable : affectedChannelGroups) { + unavailableChannels.unavailableChannelGroup( + channelGroupToMoveToTemporaryUnavailable); + } + } + startSubscribeLoop(unavailableChannels.build()); + } + + break; + default: + listenerManager.announce(status); + delayedReconnectionManager.scheduleDelayedReconnection(); + break; + } + } + + private void stopSubscribeLoop() { + cancelDelayedLoopIterationForTemporaryUnavailableChannels(); + if (subscribeCall != null) { + subscribeCall.silentCancel(); + subscribeCall = null; + } + } + + private synchronized void performHeartbeatLoop(PubSubOperation pubSubOperation) { + if (heartbeatCall != null) { + heartbeatCall.silentCancel(); + heartbeatCall = null; + } + + subscriptionState.handleOperation(pubSubOperation); + StateManager.HeartbeatStateData heartbeatStateData = subscriptionState.heartbeatStateData(); + + final List heartbeatChannels = heartbeatStateData.getHeartbeatChannels(); + final List heartbeatChannelGroups = heartbeatStateData.getHeartbeatChannelGroups(); + + + // do not start the loop if we do not have any presence channels or channel groups enabled. + if (heartbeatChannels.isEmpty() + && heartbeatChannelGroups.isEmpty()) { + return; + } + + final Map statePayload; + if (heartbeatStateData.getStatePayload().isEmpty()) { + statePayload = null; + } else { + statePayload = heartbeatStateData.getStatePayload(); + } + + heartbeatCall = new Heartbeat(pubnub, this.telemetryManager, this.retrofitManager, this.tokenManager) + .channels(heartbeatChannels) + .channelGroups(heartbeatChannelGroups) + .state(statePayload); + + heartbeatCall.async(new PNCallback() { + @Override + public void onResponse(Boolean result, @NotNull PNStatus status) { + PNHeartbeatNotificationOptions heartbeatVerbosity = + pubnub.getConfiguration().getHeartbeatNotificationOptions(); + + if (status.isError()) { + if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL + || heartbeatVerbosity == PNHeartbeatNotificationOptions.FAILURES) { + listenerManager.announce(status); + } + + // stop the heartbeating logic since an error happened. + stopHeartbeatTimer(); + + } else { + if (heartbeatVerbosity == PNHeartbeatNotificationOptions.ALL) { + listenerManager.announce(status); + } + } + } + }); + } + + public void unsubscribeAll() { + StateManager.SubscriptionStateData subscriptionStateData = subscriptionState.subscriptionStateData(false); + + adaptUnsubscribeBuilder(UnsubscribeOperation.builder() + .channelGroups(subscriptionStateData.getChannelGroups()) + .channels(subscriptionStateData.getChannels()) + .build()); + } + + private PNStatus.PNStatusBuilder createPublicStatus(PNStatus privateStatus) { + return PNStatus.builder() + .statusCode(privateStatus.getStatusCode()) + .authKey(privateStatus.getAuthKey()) + .operation(privateStatus.getOperation()) + .affectedChannels(privateStatus.getAffectedChannels()) + .affectedChannelGroups(privateStatus.getAffectedChannelGroups()) + .clientRequest(privateStatus.getClientRequest()) + .origin(privateStatus.getOrigin()) + .tlsEnabled(privateStatus.isTlsEnabled()); + } +} diff --git a/src/main/java/com/pubnub/api/managers/TelemetryManager.java b/src/main/java/com/pubnub/api/managers/TelemetryManager.java new file mode 100644 index 000000000..64b9d34aa --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/TelemetryManager.java @@ -0,0 +1,204 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.enums.PNOperationType; + +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +public class TelemetryManager { + + /** + * Timer for telemetry information clean up. + */ + private Timer timer; + + private Map>> latencies; + + private NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US); + + private static final int MAX_FRACTION_DIGITS = 3; + + private static final int TIMESTAMP_DIVIDER = 1000; + + private static final double MAXIMUM_LATENCY_DATA_AGE = 60.0f; + private static final int CLEAN_UP_INTERVAL = 1; + private static final int CLEAN_UP_INTERVAL_MULTIPLIER = 1000; + + public TelemetryManager() { + this.latencies = new HashMap<>(); + + this.numberFormat.setMaximumFractionDigits(MAX_FRACTION_DIGITS); + this.numberFormat.setRoundingMode(RoundingMode.HALF_UP); + this.numberFormat.setGroupingUsed(false); + + startCleanUpTimer(); + } + + public synchronized Map operationsLatency() { + Map operationLatencies = new HashMap<>(); + for (Map.Entry>> entry : this.latencies.entrySet()) { + String latencyKey = "l_".concat(entry.getKey()); + double endpointAverageLatency = TelemetryManager.averageLatencyFromData(entry.getValue()); + if (endpointAverageLatency > 0.0f) { + operationLatencies.put(latencyKey, numberFormat.format(endpointAverageLatency)); + } + } + return operationLatencies; + } + + public synchronized void storeLatency(long latency, PNOperationType type) { + if (type != PNOperationType.PNSubscribeOperation && latency > 0) { + String endpointName = TelemetryManager.endpointNameForOperation(type); + if (endpointName != null) { + double storeDate = (new Date()).getTime() / (double) TIMESTAMP_DIVIDER; + + List> operationLatencies = this.latencies.get(endpointName); + if (operationLatencies == null) { + operationLatencies = new ArrayList<>(); + this.latencies.put(endpointName, operationLatencies); + } + + Map latencyEntry = new HashMap<>(); + latencyEntry.put("d", storeDate); + latencyEntry.put("l", ((double) latency / TIMESTAMP_DIVIDER)); + operationLatencies.add(latencyEntry); + } + } + } + + private synchronized void cleanUpTelemetryData() { + double currentDate = (new Date()).getTime() / (double) TIMESTAMP_DIVIDER; + List endpoints = new ArrayList<>(this.latencies.keySet()); + for (String endpoint : endpoints) { + List> outdatedLatencies = new ArrayList<>(); + List> operationLatencies = this.latencies.get(endpoint); + for (Map latencyInformation : operationLatencies) { + if (currentDate - latencyInformation.get("d") > MAXIMUM_LATENCY_DATA_AGE) { + outdatedLatencies.add(latencyInformation); + } + } + if (outdatedLatencies.size() > 0) { + operationLatencies.removeAll(outdatedLatencies); + } + if (operationLatencies.size() == 0) { + this.latencies.remove(endpoint); + } + } + } + + private void startCleanUpTimer() { + long interval = CLEAN_UP_INTERVAL * CLEAN_UP_INTERVAL_MULTIPLIER; + + stopCleanUpTimer(); + this.timer = new Timer("Telemetry Manager timer", true); + this.timer.schedule(new TimerTask() { + @Override + public void run() { + cleanUpTelemetryData(); + } + }, interval, interval); + } + + public void stopCleanUpTimer() { + if (this.timer != null) { + this.timer.cancel(); + this.timer = null; + } + } + + private static double averageLatencyFromData(List> endpointLatencies) { + double totalLatency = 0.0f; + for (Map item : endpointLatencies) { + totalLatency += item.get("l"); + } + + return totalLatency / endpointLatencies.size(); + } + + private static String endpointNameForOperation(PNOperationType type) { + String endpoint; + switch (type) { + case PNPublishOperation: + endpoint = "pub"; + break; + case PNHistoryOperation: + case PNFetchMessagesOperation: + case PNDeleteMessagesOperation: + endpoint = "hist"; + break; + case PNUnsubscribeOperation: + case PNWhereNowOperation: + case PNHereNowOperation: + case PNHeartbeatOperation: + case PNSetStateOperation: + case PNGetState: + endpoint = "pres"; + break; + case PNAddChannelsToGroupOperation: + case PNRemoveChannelsFromGroupOperation: + case PNChannelGroupsOperation: + case PNRemoveGroupOperation: + case PNChannelsForGroupOperation: + endpoint = "cg"; + break; + case PNPushNotificationEnabledChannelsOperation: + case PNAddPushNotificationsOnChannelsOperation: + case PNRemovePushNotificationsFromChannelsOperation: + case PNRemoveAllPushNotificationsOperation: + endpoint = "push"; + break; + case PNAccessManagerAudit: + case PNAccessManagerGrant: + endpoint = "pam"; + break; + case PNMessageCountOperation: + endpoint = "mc"; + break; + case PNSignalOperation: + endpoint = "sig"; + break; + case PNSetUuidMetadataOperation: + case PNGetUuidMetadataOperation: + case PNGetAllUuidMetadataOperation: + case PNRemoveUuidMetadataOperation: + case PNSetChannelMetadataOperation: + case PNGetChannelMetadataOperation: + case PNGetAllChannelsMetadataOperation: + case PNRemoveChannelMetadataOperation: + case PNSetMembershipsOperation: + case PNGetMembershipsOperation: + case PNRemoveMembershipsOperation: + case PNManageMembershipsOperation: + case PNSetChannelMembersOperation: + case PNGetChannelMembersOperation: + case PNRemoveChannelMembersOperation: + case PNManageChannelMembersOperation: + endpoint = "obj"; + break; + case PNAccessManagerGrantToken: + endpoint = "pamv3"; + break; + case PNAddMessageAction: + case PNGetMessageActions: + case PNDeleteMessageAction: + endpoint = "msga"; + break; + case PNFileAction: + endpoint = "file"; + break; + default: + endpoint = "time"; + break; + } + + return endpoint; + } +} diff --git a/src/main/java/com/pubnub/api/managers/token_manager/TokenManager.java b/src/main/java/com/pubnub/api/managers/token_manager/TokenManager.java new file mode 100644 index 000000000..c3a2c3e0d --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/token_manager/TokenManager.java @@ -0,0 +1,13 @@ +package com.pubnub.api.managers.token_manager; + +public class TokenManager { + private volatile String token = null; + + public void setToken(String token) { + this.token = token; + } + + public String getToken() { + return token; + } +} diff --git a/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java new file mode 100644 index 000000000..59623923e --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java @@ -0,0 +1,39 @@ +package com.pubnub.api.managers.token_manager; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import com.pubnub.api.vendor.Base64; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_INVALID_ACCESS_TOKEN; + +public class TokenParser { + private final ObjectMapper mapper = objectMapper(); + + public PNToken unwrapToken(String token) throws PubNubException { + try { + byte[] byteArray = Base64.decode(token.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE); + return mapper.readValue(byteArray, PNToken.class); + } catch (IOException e) { + throw PubNubException.builder() + .cause(e) + .pubnubError(PNERROBJ_INVALID_ACCESS_TOKEN) + .build(); + } + } + + private ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(new CBORFactory()); + objectMapper.configOverride(Map.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return objectMapper; + } +} diff --git a/src/main/java/com/pubnub/api/models/SubscriptionItem.java b/src/main/java/com/pubnub/api/models/SubscriptionItem.java new file mode 100644 index 000000000..5ee656fa7 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/SubscriptionItem.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Setter() +@Getter +@Accessors(chain = true) +@EqualsAndHashCode +public class SubscriptionItem { + + private String name; + private Object state; + +} diff --git a/src/main/java/com/pubnub/api/models/TokenBitmask.java b/src/main/java/com/pubnub/api/models/TokenBitmask.java new file mode 100644 index 000000000..419e7664b --- /dev/null +++ b/src/main/java/com/pubnub/api/models/TokenBitmask.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models; + +public class TokenBitmask { + private TokenBitmask() { + } + + public static final int READ = 1; + public static final int WRITE = 2; + public static final int MANAGE = 4; + public static final int DELETE = 8; + public static final int CREATE = 16; + public static final int GET = 32; + public static final int UPDATE = 64; + public static final int JOIN = 128; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNBoundedPage.java b/src/main/java/com/pubnub/api/models/consumer/PNBoundedPage.java new file mode 100644 index 000000000..856eeb17d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNBoundedPage.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer; + +import lombok.Data; + +@Data +public class PNBoundedPage { + private final Long start; + private final Long end; + private final Integer limit; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNErrorData.java b/src/main/java/com/pubnub/api/models/consumer/PNErrorData.java new file mode 100644 index 000000000..480353122 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNErrorData.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.consumer; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@AllArgsConstructor +@Getter +@ToString +public class PNErrorData { + + private String information; + private Exception throwable; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNPage.java b/src/main/java/com/pubnub/api/models/consumer/PNPage.java new file mode 100644 index 000000000..e51b0de9a --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNPage.java @@ -0,0 +1,30 @@ +package com.pubnub.api.models.consumer; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public abstract class PNPage { + @Getter + protected final String hash; + + public static Next next(String hash) { + return new Next(hash); + } + + public static Previous previous(String hash) { + return new Previous(hash); + } + + public static class Next extends PNPage { + Next(String hash) { + super(hash); + } + } + + public static class Previous extends PNPage { + Previous(String hash) { + super(hash); + } + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNPublishResult.java b/src/main/java/com/pubnub/api/models/consumer/PNPublishResult.java new file mode 100644 index 000000000..22a78c6bc --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNPublishResult.java @@ -0,0 +1,9 @@ +package com.pubnub.api.models.consumer; + +import lombok.*; + +@Builder +@Data +public class PNPublishResult { + private final Long timetoken; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNStatus.java b/src/main/java/com/pubnub/api/models/consumer/PNStatus.java new file mode 100644 index 000000000..f7da29be1 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNStatus.java @@ -0,0 +1,56 @@ +package com.pubnub.api.models.consumer; + +import com.pubnub.api.endpoints.remoteaction.RemoteAction; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@Builder(toBuilder = true) +@Getter +@ToString +public class PNStatus { + + private PNStatusCategory category; + private PNErrorData errorData; + private boolean error; + + // boolean automaticallyRetry; + + private int statusCode; + private PNOperationType operation; + + private boolean tlsEnabled; + + private String uuid; + private String authKey; + private String origin; + private Object clientRequest; + + // send back channel, channel groups that were affected by this operation + @Nullable + private List affectedChannels; + @Nullable + private List affectedChannelGroups; + + @Getter(AccessLevel.NONE) + @ToString.Exclude + private RemoteAction executedEndpoint; + + + public void retry() { + executedEndpoint.retry(); + } + + /* + public void cancelAutomaticRetry() { + // TODO + } + */ + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/PNTimeResult.java b/src/main/java/com/pubnub/api/models/consumer/PNTimeResult.java new file mode 100644 index 000000000..d5c9fd2c8 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/PNTimeResult.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class PNTimeResult { + private Long timetoken; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResult.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResult.java new file mode 100644 index 000000000..dbbb1db49 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerGrantResult.java @@ -0,0 +1,25 @@ +package com.pubnub.api.models.consumer.access_manager; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.ToString; + +import java.util.Map; + +@Builder +@Getter +@ToString +@Data +public class PNAccessManagerGrantResult { + + private String level; + private int ttl; + private String subscribeKey; + + private Map> channels; + + private Map> channelGroups; + + private Map> uuids; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeyData.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeyData.java new file mode 100644 index 000000000..b6c9b5d3a --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeyData.java @@ -0,0 +1,32 @@ +package com.pubnub.api.models.consumer.access_manager; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class PNAccessManagerKeyData { + + @SerializedName("r") + private boolean readEnabled; + + @SerializedName("w") + private boolean writeEnabled; + + @SerializedName("m") + private boolean manageEnabled; + + @SerializedName("d") + private boolean deleteEnabled; + + @SerializedName("g") + private boolean getEnabled; + + @SerializedName("u") + private boolean updateEnabled; + + @SerializedName("j") + private boolean joinEnabled; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeysData.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeysData.java new file mode 100644 index 000000000..8a2b9465e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/PNAccessManagerKeysData.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.access_manager; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.ToString; + +import java.util.Map; + +@Getter +@ToString +public class PNAccessManagerKeysData { + + @SerializedName("auths") + private Map authKeys; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java new file mode 100644 index 000000000..37f5b41fd --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java @@ -0,0 +1,57 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +public class ChannelGrant extends PNResource { + + private ChannelGrant() { + + } + + public static ChannelGrant name(String channelName) { + ChannelGrant channelGrant = new ChannelGrant(); + channelGrant.resourceName = channelName; + return channelGrant; + } + + public static ChannelGrant pattern(String channelPattern) { + ChannelGrant channelGrant = new ChannelGrant(); + channelGrant.resourcePattern = channelPattern; + return channelGrant; + } + + @Override + public ChannelGrant read() { + return super.read(); + } + + @Override + public ChannelGrant delete() { + return super.delete(); + } + + @Override + public ChannelGrant write() { + return super.write(); + } + + @Override + public ChannelGrant get() { + return super.get(); + } + + @Override + public ChannelGrant manage() { + return super.manage(); + } + + @Override + public ChannelGrant update() { + return super.update(); + } + + @Override + public ChannelGrant join() { + return super.join(); + } + + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java new file mode 100644 index 000000000..edde89f27 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java @@ -0,0 +1,29 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +public class ChannelGroupGrant extends PNResource { + + private ChannelGroupGrant() { + } + + public static ChannelGroupGrant id(String groupName) { + ChannelGroupGrant channelGroupGrant = new ChannelGroupGrant(); + channelGroupGrant.resourceName = groupName; + return channelGroupGrant; + } + + public static ChannelGroupGrant pattern(String groupPattern) { + ChannelGroupGrant channelGroupGrant = new ChannelGroupGrant(); + channelGroupGrant.resourcePattern = groupPattern; + return channelGroupGrant; + } + + @Override + public ChannelGroupGrant read() { + return super.read(); + } + + @Override + public ChannelGroupGrant manage() { + return super.manage(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java new file mode 100644 index 000000000..ee079bc90 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNGrantTokenResult { + @NonNull + private final String token; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java new file mode 100644 index 000000000..9d9261722 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java @@ -0,0 +1,73 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +import lombok.AccessLevel; +import lombok.Getter; + +@Getter +public abstract class PNResource { + + @Getter(AccessLevel.NONE) + protected String resourceName; + @Getter(AccessLevel.NONE) + protected String resourcePattern; + + protected boolean read; + protected boolean write; + protected boolean create; + protected boolean delete; + protected boolean manage; + protected boolean get; + protected boolean update; + protected boolean join; + + protected T read() { + this.read = true; + return (T) this; + } + + protected T write() { + this.write = true; + return (T) this; + } + + protected T create() { + this.create = true; + return (T) this; + } + + protected T delete() { + this.delete = true; + return (T) this; + } + + protected T manage() { + this.manage = true; + return (T) this; + } + + protected T get() { + this.get = true; + return (T) this; + } + + protected T update() { + this.update = true; + return (T) this; + } + + protected T join() { + this.join = true; + return (T) this; + } + + public boolean isPatternResource() { + return resourcePattern != null; + } + + public String getId() { + if (isPatternResource()) { + return resourcePattern; + } + return resourceName; + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java new file mode 100644 index 000000000..3829c446c --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java @@ -0,0 +1,77 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.pubnub.api.models.TokenBitmask; +import lombok.Data; +import lombok.NonNull; + +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PNToken { + private final int version; + private final long timestamp; + private final long ttl; + private final String authorizedUUID; + private final Object meta; + @NonNull + private final PNTokenResources resources; + @NonNull + private final PNTokenResources patterns; + + @JsonCreator + public static PNToken of( + @JsonProperty("v") final int v, + @JsonProperty("t") final long t, + @JsonProperty("ttl") final long ttl, + @JsonProperty("res") final PNTokenResources res, + @JsonProperty("pat") final PNTokenResources pat, + @JsonProperty("uuid") final String uuid, + @JsonProperty("meta") final Object meta) { + return new PNToken(v, t, ttl, uuid, meta, res, pat); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PNTokenResources { + @NonNull + private final Map channels; + @NonNull + private final Map channelGroups; + @NonNull + private final Map uuids; + + @JsonCreator + public static PNTokenResources of(@JsonProperty("chan") final Map chan, + @JsonProperty("grp") final Map grp, + @JsonProperty("uuid") final Map uuid) { + return new PNTokenResources(chan, grp, uuid); + } + } + + @Data + public static class PNResourcePermissions { + private final boolean read; + private final boolean write; + private final boolean manage; + private final boolean delete; + private final boolean get; + private final boolean update; + private final boolean join; + + @JsonCreator + public static PNResourcePermissions of(int grant) { + return new PNResourcePermissions( + (grant & TokenBitmask.READ) != 0, + (grant & TokenBitmask.WRITE) != 0, + (grant & TokenBitmask.MANAGE) != 0, + (grant & TokenBitmask.DELETE) != 0, + (grant & TokenBitmask.GET) != 0, + (grant & TokenBitmask.UPDATE) != 0, + (grant & TokenBitmask.JOIN) != 0); + } + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/UUIDGrant.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/UUIDGrant.java new file mode 100644 index 000000000..c6055f1d9 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/UUIDGrant.java @@ -0,0 +1,34 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +public class UUIDGrant extends PNResource { + + private UUIDGrant() { + } + + public static UUIDGrant id(String groupName) { + UUIDGrant uuidGrant = new UUIDGrant(); + uuidGrant.resourceName = groupName; + return uuidGrant; + } + + public static UUIDGrant pattern(String groupPattern) { + UUIDGrant uuidGrant = new UUIDGrant(); + uuidGrant.resourcePattern = groupPattern; + return uuidGrant; + } + + @Override + public UUIDGrant get() { + return super.get(); + } + + @Override + public UUIDGrant update() { + return super.update(); + } + + @Override + public UUIDGrant delete() { + return super.delete(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAddChannelResult.java b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAddChannelResult.java new file mode 100644 index 000000000..fe80aa50e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAddChannelResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.channel_group; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNChannelGroupsAddChannelResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAllChannelsResult.java b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAllChannelsResult.java new file mode 100644 index 000000000..775c7a0b1 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsAllChannelsResult.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.channel_group; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Builder +@ToString +public class PNChannelGroupsAllChannelsResult { + private List channels; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsDeleteGroupResult.java b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsDeleteGroupResult.java new file mode 100644 index 000000000..136b6b3ad --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsDeleteGroupResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.channel_group; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNChannelGroupsDeleteGroupResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsListAllResult.java b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsListAllResult.java new file mode 100644 index 000000000..4ef24dc5a --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsListAllResult.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.channel_group; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Builder +@Getter +@ToString +public class PNChannelGroupsListAllResult { + private List groups; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsRemoveChannelResult.java b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsRemoveChannelResult.java new file mode 100644 index 000000000..55ffa4a9e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/channel_group/PNChannelGroupsRemoveChannelResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.channel_group; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNChannelGroupsRemoveChannelResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNBaseFile.java b/src/main/java/com/pubnub/api/models/consumer/files/PNBaseFile.java new file mode 100644 index 000000000..fab11ebd9 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNBaseFile.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNBaseFile implements PNFile { + @NonNull + private final String id; + @NonNull + private final String name; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNDeleteFileResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNDeleteFileResult.java new file mode 100644 index 000000000..3747dd6bc --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNDeleteFileResult.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNDeleteFileResult { + @NonNull + private final int status; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadFileResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadFileResult.java new file mode 100644 index 000000000..c7ce5fee0 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadFileResult.java @@ -0,0 +1,13 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +import java.io.InputStream; + +@Data +public class PNDownloadFileResult { + @NonNull + private final String fileName; + private final InputStream byteStream; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadableFile.java b/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadableFile.java new file mode 100644 index 000000000..72111364a --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNDownloadableFile.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNDownloadableFile implements PNFile { + @NonNull + private final String id; + @NonNull + private final String name; + @NonNull + private final String url; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNFile.java b/src/main/java/com/pubnub/api/models/consumer/files/PNFile.java new file mode 100644 index 000000000..137a12744 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNFile.java @@ -0,0 +1,6 @@ +package com.pubnub.api.models.consumer.files; + +public interface PNFile { + String getId(); + String getName(); +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNFileUploadResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNFileUploadResult.java new file mode 100644 index 000000000..f4ded3fac --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNFileUploadResult.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNFileUploadResult { + @NonNull + private final long timetoken; + @NonNull + private final int status; + @NonNull + private final PNBaseFile file; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNFileUrlResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNFileUrlResult.java new file mode 100644 index 000000000..cecfef99a --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNFileUrlResult.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNFileUrlResult { + @NonNull + private final String url; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNListFilesResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNListFilesResult.java new file mode 100644 index 000000000..d7c0c5ad4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNListFilesResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.files; + +import com.pubnub.api.models.consumer.PNPage; +import lombok.Data; +import lombok.NonNull; + +import java.util.Collection; + +@Data +public class PNListFilesResult { + @NonNull + private final int count; + private final PNPage.Next next; + @NonNull + private final int status; + @NonNull + private final Collection data; +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNPublishFileMessageResult.java b/src/main/java/com/pubnub/api/models/consumer/files/PNPublishFileMessageResult.java new file mode 100644 index 000000000..4358f3fa7 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNPublishFileMessageResult.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNPublishFileMessageResult { + @NonNull + private final long timetoken; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/files/PNUploadedFile.java b/src/main/java/com/pubnub/api/models/consumer/files/PNUploadedFile.java new file mode 100644 index 000000000..4b3c2e36d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/files/PNUploadedFile.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.files; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class PNUploadedFile implements PNFile { + @NonNull + private final String id; + @NonNull + private final String name; + @NonNull + private final Integer size; + @NonNull + private final String created; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.java b/src/main/java/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.java new file mode 100644 index 000000000..c27556d80 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNDeleteMessagesResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.history; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class PNDeleteMessagesResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessageItem.java b/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessageItem.java new file mode 100644 index 000000000..9a8503249 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessageItem.java @@ -0,0 +1,38 @@ +package com.pubnub.api.models.consumer.history; + +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +import java.util.HashMap; +import java.util.List; + +@Builder(toBuilder = true) +@Data +public class PNFetchMessageItem { + + private final JsonElement message; + private final JsonElement meta; + private final Long timetoken; + private final HashMap>> actions; + private final String uuid; + @SerializedName("message_type") + @Getter(AccessLevel.NONE) + private final String messageType; + private int getMessageType() { + if (messageType == null || messageType.isEmpty()) { + return 0; + } else { + return Integer.parseInt(messageType); + } + } + + @Data + public static class Action { + private final String uuid; + private final String actionTimetoken; + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessagesResult.java b/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessagesResult.java new file mode 100644 index 000000000..7a1d52439 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNFetchMessagesResult.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.consumer.history; + +import com.pubnub.api.models.consumer.PNBoundedPage; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +import java.util.List; +import java.util.Map; + +@ToString +@Data +@Builder +public class PNFetchMessagesResult { + private final Map> channels; + private final PNBoundedPage page; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryItemResult.java b/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryItemResult.java new file mode 100644 index 000000000..b975620aa --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryItemResult.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.history; + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNHistoryItemResult { + + private Long timetoken; + private JsonElement entry; + private JsonElement meta; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryResult.java b/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryResult.java new file mode 100644 index 000000000..4f3744c05 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNHistoryResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.history; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Builder +@ToString +public class PNHistoryResult { + + private List messages; + private Long startTimetoken; + private Long endTimetoken; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/history/PNMessageCountResult.java b/src/main/java/com/pubnub/api/models/consumer/history/PNMessageCountResult.java new file mode 100644 index 000000000..be60c8dbe --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/history/PNMessageCountResult.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.history; + +import java.util.Map; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class PNMessageCountResult { + + private Map channels; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.java b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.java new file mode 100644 index 000000000..61319beaa --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNAddMessageActionResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.message_actions; + +import lombok.Builder; + +public class PNAddMessageActionResult extends PNMessageAction { + + @Builder + private PNAddMessageActionResult(PNMessageAction pnMessageAction) { + super(pnMessageAction); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.java b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.java new file mode 100644 index 000000000..74fedd086 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNGetMessageActionsResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.message_actions; + +import com.google.gson.annotations.SerializedName; +import com.pubnub.api.models.consumer.PNBoundedPage; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Builder +@Data +public class PNGetMessageActionsResult { + + @SerializedName("data") + private final List actions; + @SerializedName("more") + private final PNBoundedPage page; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/message_actions/PNMessageAction.java b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNMessageAction.java new file mode 100644 index 000000000..5f8610565 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNMessageAction.java @@ -0,0 +1,34 @@ +package com.pubnub.api.models.consumer.message_actions; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@ToString +@Accessors(chain = true) +public class PNMessageAction { + + @Setter + private String type; + @Setter + private String value; + @Setter + private Long messageTimetoken; + + private String uuid; + private Long actionTimetoken; + + public PNMessageAction() { + + } + + PNMessageAction(PNMessageAction pnMessageAction) { + this.type = pnMessageAction.type; + this.value = pnMessageAction.value; + this.uuid = pnMessageAction.uuid; + this.actionTimetoken = pnMessageAction.actionTimetoken; + this.messageTimetoken = pnMessageAction.messageTimetoken; + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.java b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.java new file mode 100644 index 000000000..5366cd342 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/message_actions/PNRemoveMessageActionResult.java @@ -0,0 +1,5 @@ +package com.pubnub.api.models.consumer.message_actions; + +public class PNRemoveMessageActionResult { + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/PNObject.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/PNObject.java new file mode 100644 index 000000000..b5ad1c2c8 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/PNObject.java @@ -0,0 +1,33 @@ +package com.pubnub.api.models.consumer.objects_api; + +import com.google.gson.annotations.JsonAdapter; +import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Accessors(chain = true) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +public class PNObject { + + @EqualsAndHashCode.Include + protected String id; + + @JsonAdapter(CustomPayloadJsonInterceptor.class) + @Setter + protected Object custom; + + protected String updated; + protected String eTag; + + protected PNObject(String id) { + this.id = id; + } + + protected PNObject() { + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java new file mode 100644 index 000000000..09b1f3045 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadata.java @@ -0,0 +1,36 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.pubnub.api.models.consumer.objects_api.PNObject; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PNChannelMetadata extends PNObject { + private String name; + private String description; + + public PNChannelMetadata(String id, String name, String description) { + super(id); + this.name = name; + this.description = description; + } + + public PNChannelMetadata(String id, String name) { + this(id, name, null); + } + + @Override + public PNChannelMetadata setCustom(Object custom) { + super.setCustom(custom); + return this; + } + + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadataResult.java new file mode 100644 index 000000000..c8f5a98e6 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNChannelMetadataResult.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult; +import lombok.ToString; + +@ToString(callSuper = true) +public class PNChannelMetadataResult extends ObjectResult { + public PNChannelMetadataResult(BasePubSubResult result, String event, PNChannelMetadata data) { + super(result, event, data); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetAllChannelsMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetAllChannelsMetadataResult.java new file mode 100644 index 000000000..2290c3d25 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetAllChannelsMetadataResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.*; + +@Getter +@Setter(AccessLevel.PACKAGE) +@NoArgsConstructor +@ToString(callSuper = true) +public class PNGetAllChannelsMetadataResult extends EntityArrayEnvelope { + + public PNGetAllChannelsMetadataResult(EntityArrayEnvelope envelope) { + this.status = envelope.getStatus(); + this.totalCount = envelope.getTotalCount(); + this.prev = envelope.getPrev(); + this.next = envelope.getNext(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetChannelMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetChannelMetadataResult.java new file mode 100644 index 000000000..bfa9f71ca --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNGetChannelMetadataResult.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@NoArgsConstructor +@ToString +public class PNGetChannelMetadataResult extends EntityEnvelope { + public PNGetChannelMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNRemoveChannelMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNRemoveChannelMetadataResult.java new file mode 100644 index 000000000..0316e341f --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNRemoveChannelMetadataResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter(AccessLevel.PACKAGE) +@ToString(callSuper = true) +public class PNRemoveChannelMetadataResult extends EntityEnvelope { + public PNRemoveChannelMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNSetChannelMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNSetChannelMetadataResult.java new file mode 100644 index 000000000..b11fa370f --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/channel/PNSetChannelMetadataResult.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.objects_api.channel; + +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@NoArgsConstructor +@ToString +public class PNSetChannelMetadataResult extends EntityEnvelope { + public PNSetChannelMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNGetChannelMembersResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNGetChannelMembersResult.java new file mode 100644 index 000000000..f72cd5354 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNGetChannelMembersResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString +public class PNGetChannelMembersResult extends EntityArrayEnvelope { + public PNGetChannelMembersResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNManageChannelMembersResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNManageChannelMembersResult.java new file mode 100644 index 000000000..4bf6280a4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNManageChannelMembersResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString(callSuper = true) +public class PNManageChannelMembersResult extends EntityArrayEnvelope { + public PNManageChannelMembersResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNMembers.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNMembers.java new file mode 100644 index 000000000..e3759b9d4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNMembers.java @@ -0,0 +1,25 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.google.gson.annotations.JsonAdapter; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Accessors(chain = true) +@EqualsAndHashCode +@ToString +@RequiredArgsConstructor +public class PNMembers { + private PNUUIDMetadata uuid; + + @JsonAdapter(CustomPayloadJsonInterceptor.class) + protected Object custom; + + protected String updated; + protected String eTag; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNRemoveChannelMembersResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNRemoveChannelMembersResult.java new file mode 100644 index 000000000..fe5c21b62 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNRemoveChannelMembersResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString +public class PNRemoveChannelMembersResult extends EntityArrayEnvelope { + public PNRemoveChannelMembersResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNSetChannelMembersResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNSetChannelMembersResult.java new file mode 100644 index 000000000..fafbf7258 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNSetChannelMembersResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString +public class PNSetChannelMembersResult extends EntityArrayEnvelope { + public PNSetChannelMembersResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNUUID.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNUUID.java new file mode 100644 index 000000000..100be38e2 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/member/PNUUID.java @@ -0,0 +1,51 @@ +package com.pubnub.api.models.consumer.objects_api.member; + +import com.google.gson.annotations.JsonAdapter; +import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@RequiredArgsConstructor +public abstract class PNUUID { + @AllArgsConstructor + @EqualsAndHashCode + @Getter + public static class UUIDId { + private String id; + } + + @Getter + private final UUIDId uuid; + + public static PNUUID uuid(final String uuid) { + return new JustUUID(new UUIDId(uuid)); + } + public static PNUUID uuidWithCustom(final String uuid, final Map custom) { + return new UUIDWithCustom(new UUIDId(uuid), new HashMap<>(custom)); + } + + @Getter + @EqualsAndHashCode(callSuper = true) + public static class JustUUID extends PNUUID { + JustUUID(UUIDId uuid) { + super(uuid); + } + } + + @Getter + @EqualsAndHashCode(callSuper = true) + public static class UUIDWithCustom extends PNUUID { + @JsonAdapter(CustomPayloadJsonInterceptor.class) + private final Object custom; + + UUIDWithCustom(UUIDId uuid, Object custom) { + super(uuid); + this.custom = custom; + } + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNChannelMembership.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNChannelMembership.java new file mode 100644 index 000000000..9a5e6caf9 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNChannelMembership.java @@ -0,0 +1,49 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.google.gson.annotations.JsonAdapter; +import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import lombok.*; + +import java.util.HashMap; +import java.util.Map; + +@RequiredArgsConstructor +public abstract class PNChannelMembership { + @AllArgsConstructor + @EqualsAndHashCode + @Getter + public static class ChannelId { + private String id; + } + + @Getter + private final ChannelId channel; + + public static PNChannelMembership channel(final String channelId) { + return new JustChannel(new ChannelId(channelId)); + } + + public static PNChannelMembership channelWithCustom(final String channelId, final Map custom) { + return new ChannelWithCustom(new ChannelId(channelId), new HashMap<>(custom)); + } + + @Getter + @EqualsAndHashCode(callSuper = true) + public static class JustChannel extends PNChannelMembership { + JustChannel(@NonNull final ChannelId channelId) { + super(channelId); + } + } + + @Getter + @EqualsAndHashCode(callSuper = true) + public static class ChannelWithCustom extends PNChannelMembership { + @JsonAdapter(CustomPayloadJsonInterceptor.class) + private final Object custom; + + ChannelWithCustom(@NonNull final ChannelId channelId, @NonNull Object custom) { + super(channelId); + this.custom = custom; + } + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMemberships.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMemberships.java new file mode 100644 index 000000000..ae4f467d0 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMemberships.java @@ -0,0 +1,7 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.consumer.objects_api.PNObject; + +public class PNGetMemberships extends PNObject { + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMembershipsResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMembershipsResult.java new file mode 100644 index 000000000..3e1d7a5cb --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNGetMembershipsResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString +public class PNGetMembershipsResult extends EntityArrayEnvelope { + public PNGetMembershipsResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNManageMembershipResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNManageMembershipResult.java new file mode 100644 index 000000000..7a4add01d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNManageMembershipResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString(callSuper = true) +public class PNManageMembershipResult extends EntityArrayEnvelope { + public PNManageMembershipResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java new file mode 100644 index 000000000..35ca8af8d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java @@ -0,0 +1,25 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.google.gson.annotations.JsonAdapter; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import com.pubnub.api.utils.UnwrapSingleField; +import lombok.*; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@RequiredArgsConstructor +public class PNMembership { + @NonNull + private PNChannelMetadata channel; + + @JsonAdapter(CustomPayloadJsonInterceptor.class) + protected Object custom; + + @JsonAdapter(UnwrapSingleField.class) + protected String uuid; + + protected String updated; + protected String eTag; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembershipResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembershipResult.java new file mode 100644 index 000000000..5192559aa --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembershipResult.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult; +import lombok.ToString; + +@ToString(callSuper = true) +public class PNMembershipResult extends ObjectResult { + public PNMembershipResult(BasePubSubResult result, String event, PNMembership data) { + super(result, event, data); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNRemoveMembershipResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNRemoveMembershipResult.java new file mode 100644 index 000000000..433118960 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNRemoveMembershipResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString(callSuper = true) +public class PNRemoveMembershipResult extends EntityArrayEnvelope { + public PNRemoveMembershipResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNSetMembershipResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNSetMembershipResult.java new file mode 100644 index 000000000..a170c8f55 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNSetMembershipResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.membership; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@ToString(callSuper = true) +public class PNSetMembershipResult extends EntityArrayEnvelope { + public PNSetMembershipResult(EntityArrayEnvelope body) { + this.data = body.getData(); + this.next = body.getNext(); + this.prev = body.getPrev(); + this.status = body.getStatus(); + this.totalCount = body.getTotalCount(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/util/CustomPayloadJsonInterceptor.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/util/CustomPayloadJsonInterceptor.java new file mode 100644 index 000000000..d6d57593e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/util/CustomPayloadJsonInterceptor.java @@ -0,0 +1,25 @@ +package com.pubnub.api.models.consumer.objects_api.util; + +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; + +public class CustomPayloadJsonInterceptor implements JsonDeserializer, JsonSerializer { + + @Override + public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return json; + } + + @Override + public JsonElement serialize(Object o, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonParser().parse(new Gson().toJson(o)); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetAllUUIDMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetAllUUIDMetadataResult.java new file mode 100644 index 000000000..3c3ae7a5c --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetAllUUIDMetadataResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import lombok.*; + +@Getter +@Setter(AccessLevel.PACKAGE) +@NoArgsConstructor +@ToString(callSuper = true) +public class PNGetAllUUIDMetadataResult extends EntityArrayEnvelope { + + public PNGetAllUUIDMetadataResult(EntityArrayEnvelope envelope) { + this.status = envelope.getStatus(); + this.totalCount = envelope.getTotalCount(); + this.prev = envelope.getPrev(); + this.next = envelope.getNext(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetUUIDMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetUUIDMetadataResult.java new file mode 100644 index 000000000..0cd01f515 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNGetUUIDMetadataResult.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.*; + +@Getter +@Setter(AccessLevel.PACKAGE) +@NoArgsConstructor +@ToString(callSuper = true) +public class PNGetUUIDMetadataResult extends EntityEnvelope { + public PNGetUUIDMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNRemoveUUIDMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNRemoveUUIDMetadataResult.java new file mode 100644 index 000000000..5b5a707c4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNRemoveUUIDMetadataResult.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.*; + +@Getter +@Setter(AccessLevel.PACKAGE) +@ToString(callSuper = true) +public class PNRemoveUUIDMetadataResult extends EntityEnvelope { + public PNRemoveUUIDMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNSetUUIDMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNSetUUIDMetadataResult.java new file mode 100644 index 000000000..484573dd3 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNSetUUIDMetadataResult.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import lombok.*; + +@Getter +@Setter(AccessLevel.PACKAGE) +@NoArgsConstructor +@ToString(callSuper = true) +public class PNSetUUIDMetadataResult extends EntityEnvelope { + public PNSetUUIDMetadataResult(final EntityEnvelope envelope) { + this.status = envelope.getStatus(); + this.data = envelope.getData(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java new file mode 100644 index 000000000..631e3b946 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadata.java @@ -0,0 +1,30 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.pubnub.api.models.consumer.objects_api.PNObject; +import lombok.*; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@NoArgsConstructor +public class PNUUIDMetadata extends PNObject { + private String name; + private String email; + private String externalId; + private String profileUrl; + + public PNUUIDMetadata(String id, String name) { + super(id); + this.name = name; + } + + @Override + public PNUUIDMetadata setCustom(Object custom) { + super.setCustom(custom); + return this; + } +} + diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadataResult.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadataResult.java new file mode 100644 index 000000000..ede7f2446 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/uuid/PNUUIDMetadataResult.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.consumer.objects_api.uuid; + +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult; +import lombok.ToString; + +@ToString(callSuper = true) +public class PNUUIDMetadataResult extends ObjectResult { + public PNUUIDMetadataResult(BasePubSubResult result, String event, PNUUIDMetadata data) { + super(result, event, data); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNGetStateResult.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNGetStateResult.java new file mode 100644 index 000000000..8563d4b8e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNGetStateResult.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.consumer.presence; + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.Map; + +@Builder +@Getter +@ToString +public class PNGetStateResult { + + private Map stateByUUID; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowChannelData.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowChannelData.java new file mode 100644 index 000000000..b9c9a1ada --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowChannelData.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.presence; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Builder +@ToString +public class PNHereNowChannelData { + + private String channelName; + private int occupancy; + private List occupants; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowOccupantData.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowOccupantData.java new file mode 100644 index 000000000..9a3730e7f --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowOccupantData.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.presence; + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class PNHereNowOccupantData { + private String uuid; + private JsonElement state; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowResult.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowResult.java new file mode 100644 index 000000000..3b4383007 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNHereNowResult.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.consumer.presence; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.Map; + +@Getter +@Builder +@ToString +public class PNHereNowResult { + private int totalChannels; + private int totalOccupancy; + private Map channels; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNSetStateResult.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNSetStateResult.java new file mode 100644 index 000000000..0d32640ee --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNSetStateResult.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.consumer.presence; + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNSetStateResult { + + private JsonElement state; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/presence/PNWhereNowResult.java b/src/main/java/com/pubnub/api/models/consumer/presence/PNWhereNowResult.java new file mode 100644 index 000000000..633333983 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/presence/PNWhereNowResult.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.presence; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Builder +@ToString +public class PNWhereNowResult { + private List channels; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.java new file mode 100644 index 000000000..c9f847749 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/BasePubSubResult.java @@ -0,0 +1,39 @@ +package com.pubnub.api.models.consumer.pubsub; + +import com.google.gson.JsonElement; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +@AllArgsConstructor +public class BasePubSubResult { + + @Deprecated + private String subscribedChannel; + @Deprecated + private String actualChannel; + + private String channel; + private String subscription; + + private Long timetoken; + + private JsonElement userMetadata; + + private String publisher; + + protected BasePubSubResult(BasePubSubResult basePubSubResult) { + this.subscribedChannel = basePubSubResult.subscribedChannel; + this.actualChannel = basePubSubResult.actualChannel; + this.channel = basePubSubResult.channel; + this.subscription = basePubSubResult.subscription; + this.timetoken = basePubSubResult.timetoken; + this.userMetadata = basePubSubResult.userMetadata; + this.publisher = basePubSubResult.publisher; + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/MessageResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/MessageResult.java new file mode 100644 index 000000000..fecb7abe6 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/MessageResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.consumer.pubsub; + +import com.google.gson.JsonElement; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString(callSuper = true) +public class MessageResult extends BasePubSubResult { + + private JsonElement message; + + public MessageResult(BasePubSubResult basePubSubResult, JsonElement message) { + super(basePubSubResult); + this.message = message; + } +} + diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/PNMessageResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNMessageResult.java new file mode 100644 index 000000000..140db3bc4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNMessageResult.java @@ -0,0 +1,14 @@ +package com.pubnub.api.models.consumer.pubsub; + +import com.google.gson.JsonElement; + +import lombok.ToString; + +@ToString(callSuper = true) +public class PNMessageResult extends MessageResult { + + public PNMessageResult(BasePubSubResult basePubSubResult, JsonElement message) { + super(basePubSubResult, message); + } +} + diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.java new file mode 100644 index 000000000..ef8b744b1 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNPresenceEventResult.java @@ -0,0 +1,39 @@ +package com.pubnub.api.models.consumer.pubsub; + +import com.google.gson.JsonElement; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Builder +@ToString +@Data +public class PNPresenceEventResult { + + private String event; + + private String uuid; + private Long timestamp; + private Integer occupancy; + private JsonElement state; + + @Deprecated + private String subscribedChannel; + @Deprecated + private String actualChannel; + + private String channel; + private String subscription; + + private Long timetoken; + private Object userMetadata; + private List join; + private List leave; + private List timeout; + private Boolean hereNowRefresh; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/PNSignalResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNSignalResult.java new file mode 100644 index 000000000..d818865ea --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/PNSignalResult.java @@ -0,0 +1,13 @@ +package com.pubnub.api.models.consumer.pubsub; + +import com.google.gson.JsonElement; + +import lombok.ToString; + +@ToString(callSuper = true) +public class PNSignalResult extends MessageResult { + + public PNSignalResult(BasePubSubResult basePubSubResult, JsonElement message) { + super(basePubSubResult, message); + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/files/PNFileEventResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/files/PNFileEventResult.java new file mode 100644 index 000000000..9c0788f5f --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/files/PNFileEventResult.java @@ -0,0 +1,19 @@ +package com.pubnub.api.models.consumer.pubsub.files; + +import com.pubnub.api.models.consumer.files.PNDownloadableFile; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; + +@Data +@Builder +public class PNFileEventResult { + @NonNull + private final String channel; + @NonNull + private final Long timetoken; + private final String publisher; + private final Object message; + @NonNull + private final PNDownloadableFile file; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.java new file mode 100644 index 000000000..f2234c336 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/message_actions/PNMessageActionResult.java @@ -0,0 +1,20 @@ +package com.pubnub.api.models.consumer.pubsub.message_actions; + +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import com.pubnub.api.models.consumer.pubsub.objects.ObjectResult; +import lombok.Builder; +import lombok.ToString; + +@ToString(callSuper = true) +public class PNMessageActionResult extends ObjectResult { + + @Builder(builderMethodName = "actionBuilder") + private PNMessageActionResult(BasePubSubResult result, String event, PNMessageAction data) { + super(result, event, data); + } + + public PNMessageAction getMessageAction() { + return data; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.java new file mode 100644 index 000000000..5362571a1 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectPayload.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.pubsub.objects; + +import com.google.gson.JsonElement; + +import lombok.Data; + +@Data +public class ObjectPayload { + + private String source; + private String version; + private String event; + private String type; + private JsonElement data; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java new file mode 100644 index 000000000..c5bb3d5c6 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java @@ -0,0 +1,20 @@ +package com.pubnub.api.models.consumer.pubsub.objects; + +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import lombok.Getter; +import lombok.ToString; + +@ToString(callSuper = true) +public abstract class ObjectResult extends BasePubSubResult { + + @Getter + protected String event; + @Getter + protected T data; + + public ObjectResult(BasePubSubResult result, String event, T data) { + super(result); + this.event = event; + this.data = data; + } +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/consumer/push/PNPushAddChannelResult.java b/src/main/java/com/pubnub/api/models/consumer/push/PNPushAddChannelResult.java new file mode 100644 index 000000000..43004dd71 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/PNPushAddChannelResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.push; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNPushAddChannelResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/push/PNPushListProvisionsResult.java b/src/main/java/com/pubnub/api/models/consumer/push/PNPushListProvisionsResult.java new file mode 100644 index 000000000..ea48e5255 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/PNPushListProvisionsResult.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.consumer.push; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Builder +@Getter +@ToString +public class PNPushListProvisionsResult { + + private List channels; + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveAllChannelsResult.java b/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveAllChannelsResult.java new file mode 100644 index 000000000..c87287951 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveAllChannelsResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.push; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNPushRemoveAllChannelsResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveChannelResult.java b/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveChannelResult.java new file mode 100644 index 000000000..635b88ef3 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/PNPushRemoveChannelResult.java @@ -0,0 +1,11 @@ +package com.pubnub.api.models.consumer.push; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder +@Getter +@ToString +public class PNPushRemoveChannelResult { +} diff --git a/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.java b/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.java new file mode 100644 index 000000000..f99116818 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadHelper.java @@ -0,0 +1,279 @@ +package com.pubnub.api.models.consumer.push.payload; + +import com.pubnub.api.enums.PNPushEnvironment; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Setter +@Accessors(chain = true) +public class PushPayloadHelper { + + private Map commonPayload; + private FCMPayload fcmPayload; + private MPNSPayload mpnsPayload; + private APNSPayload apnsPayload; + + public Map build() { + Map payload = new HashMap<>(); + + if (apnsPayload != null) { + Map apnsMap = apnsPayload.toMap(); + if (!apnsMap.isEmpty()) { + payload.put("pn_apns", apnsMap); + } + } + + if (fcmPayload != null) { + Map fcmMap = fcmPayload.toMap(); + if (!fcmMap.isEmpty()) { + payload.put("pn_gcm", fcmMap); + } + } + + if (mpnsPayload != null) { + Map mpnsMap = mpnsPayload.toMap(); + if (!mpnsMap.isEmpty()) { + payload.put("pn_mpns", mpnsMap); + } + } + + payload.putAll(filterNonNullEntries(commonPayload)); + + return payload; + } + + @Setter + @Accessors(chain = true) + public static class APNSPayload implements PushPayloadSerializer { + private APS aps; + private List apns2Configurations; + private Map custom; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + if (aps != null) { + Map apsMap = aps.toMap(); + if (apsMap != null && !apsMap.isEmpty()) { + map.put("aps", apsMap); + } + } + if (apns2Configurations != null) { + List> pnPushArray = new ArrayList<>(); + for (APNS2Configuration configuration : apns2Configurations) { + Map pushItemMap = configuration.toMap(); + if (pushItemMap != null && !pushItemMap.isEmpty()) { + pnPushArray.add(pushItemMap); + } + } + map.put("pn_push", pnPushArray); + } + map.putAll(filterNonNullEntries(custom)); + return map; + } + + @Setter + @Accessors(chain = true) + public static class APS implements PushPayloadSerializer { + private Object alert; + private Integer badge; + private String sound; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + if (alert != null) { + map.put("alert", alert); + } + if (badge != null) { + map.put("badge", badge); + } + if (sound != null) { + map.put("sound", sound); + } + return map; + } + } + + @Setter + @Accessors(chain = true) + public static class APNS2Configuration implements PushPayloadSerializer { + private String collapseId; + private String expiration; + private List targets; + private String version; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + + if (collapseId != null) { + map.put("collapse_id", collapseId); + } + if (expiration != null) { + map.put("expiration", expiration); + } + + if (targets != null && !targets.isEmpty()) { + List> targetsList = new ArrayList<>(); + for (Target target : targets) { + Map targetMap = target.toMap(); + if (targetMap != null && !targetMap.isEmpty()) { + targetsList.add(targetMap); + } + } + map.put("targets", targetsList); + } + + if (version != null) { + map.put("version", version); + } + + return map; + } + + @Setter + @Accessors(chain = true) + public static class Target implements PushPayloadSerializer { + private String topic; + private List excludeDevices; + private PNPushEnvironment environment; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + if (topic != null) { + map.put("topic", topic); + } + if (excludeDevices != null && !excludeDevices.isEmpty()) { + map.put("excluded_devices", excludeDevices); + } + if (environment != null) { + map.put("environment", environment.name().toLowerCase()); + } + return map; + } + } + } + } + + // MPNS + + @Setter + @Accessors(chain = true) + public static class MPNSPayload implements PushPayloadSerializer { + private Integer count; + private String backTitle; + private String title; + private String backContent; + private String type; + private Map custom; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + if (count != null) { + map.put("count", count); + } + if (backTitle != null) { + map.put("back_title", backTitle); + } + if (title != null) { + map.put("title", title); + } + if (backContent != null) { + map.put("back_content", backContent); + } + if (type != null) { + map.put("type", type); + } + map.putAll(filterNonNullEntries(custom)); + return map; + } + } + + // FCM + + @Setter + @Accessors(chain = true) + public static class FCMPayload implements PushPayloadSerializer { + private Map custom; + private Map data; + private Notification notification; + + @Override + public Map toMap() { + Map map = new HashMap<>(); + if (notification != null) { + Map notificationMap = notification.toMap(); + if (notificationMap != null && !notificationMap.isEmpty()) { + map.put("notification", notification.toMap()); + } + } + if (data != null && !data.isEmpty()) { + map.put("data", data); + } + map.putAll(filterNonNullEntries(custom)); + return map; + } + + @Setter + @Accessors(chain = true) + public static class Notification implements PushPayloadSerializer { + private Map parametersMap = new HashMap(); + + public Notification set(String parameterName, Object parameterValue) { + parametersMap.put(parameterName, parameterValue); + return this; + } + + public Notification setTitle(String title) { + set("title", title); + return this; + } + + public Notification setBody(String body) { + set("body", body); + return this; + } + public Notification setImage(String image) { + set("image", image); + return this; + } + public Notification setClickAction(String clickAction) { + set("click_action", clickAction); + return this; + } + + @Override + public Map toMap() { + return filterNonNullEntries(parametersMap); + } + } + } + + + // todo mapof + + private static Map filterNonNullEntries(Map targetMap) { + if (targetMap == null) { + return new HashMap<>(); + } + + Map map = new HashMap<>(); + + for (Map.Entry entry : targetMap.entrySet()) { + if (entry.getValue() != null) { + map.put(entry.getKey(), entry.getValue()); + } + } + + return map; + } + +} diff --git a/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.java b/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.java new file mode 100644 index 000000000..ebe62b8c4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/push/payload/PushPayloadSerializer.java @@ -0,0 +1,9 @@ +package com.pubnub.api.models.consumer.push.payload; + +import java.util.Map; + +interface PushPayloadSerializer { + + Map toMap(); + +} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/server/DeleteMessagesEnvelope.java b/src/main/java/com/pubnub/api/models/server/DeleteMessagesEnvelope.java new file mode 100644 index 000000000..9200d096d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/DeleteMessagesEnvelope.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class DeleteMessagesEnvelope { + + private Integer status; + private boolean error; + @SerializedName("error_message") + private String errorMessage; +} diff --git a/src/main/java/com/pubnub/api/models/server/Envelope.java b/src/main/java/com/pubnub/api/models/server/Envelope.java new file mode 100644 index 000000000..2bc3b6c96 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/Envelope.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.server; + +import com.google.gson.JsonElement; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Envelope { + private int status; + private String message; + private String service; + private T payload; + private int occupancy; + private JsonElement uuids; + private String action; + private boolean error; +} diff --git a/src/main/java/com/pubnub/api/models/server/FetchMessagesEnvelope.java b/src/main/java/com/pubnub/api/models/server/FetchMessagesEnvelope.java new file mode 100644 index 000000000..a104ea05f --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/FetchMessagesEnvelope.java @@ -0,0 +1,20 @@ +package com.pubnub.api.models.server; + +import com.pubnub.api.models.consumer.history.PNFetchMessageItem; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class FetchMessagesEnvelope { + private Map> channels; + private FetchMessagesPage more; + + @Data + public static class FetchMessagesPage { + private Long start; + private Long end; + private Integer max; + } +} diff --git a/src/main/java/com/pubnub/api/models/server/OriginationMetaData.java b/src/main/java/com/pubnub/api/models/server/OriginationMetaData.java new file mode 100644 index 000000000..cdfb37874 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/OriginationMetaData.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class OriginationMetaData { + + @SerializedName("t") + private Long timetoken; + + @SerializedName("r") + private Integer region; + +} diff --git a/src/main/java/com/pubnub/api/models/server/PresenceEnvelope.java b/src/main/java/com/pubnub/api/models/server/PresenceEnvelope.java new file mode 100644 index 000000000..f6ee6a0b4 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/PresenceEnvelope.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server; + +import com.google.gson.JsonElement; +import lombok.Getter; + +@Getter +public class PresenceEnvelope { + + private String action; + private String uuid; + private Integer occupancy; + private Long timestamp; + private JsonElement data; + +} diff --git a/src/main/java/com/pubnub/api/models/server/PublishMetaData.java b/src/main/java/com/pubnub/api/models/server/PublishMetaData.java new file mode 100644 index 000000000..9f39f87aa --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/PublishMetaData.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class PublishMetaData { + + @SerializedName("t") + private Long publishTimetoken; + + @SerializedName("r") + private Integer region; + +} diff --git a/src/main/java/com/pubnub/api/models/server/SubscribeEnvelope.java b/src/main/java/com/pubnub/api/models/server/SubscribeEnvelope.java new file mode 100644 index 000000000..1e7368a66 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/SubscribeEnvelope.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.server; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.util.List; + +@Data +public class SubscribeEnvelope { + + @SerializedName("m") + private final List messages; + + @SerializedName("t") + private final SubscribeMetadata metadata; + +} diff --git a/src/main/java/com/pubnub/api/models/server/SubscribeMessage.java b/src/main/java/com/pubnub/api/models/server/SubscribeMessage.java new file mode 100644 index 000000000..46df0f780 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/SubscribeMessage.java @@ -0,0 +1,63 @@ +package com.pubnub.api.models.server; + +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.pubnub.api.workers.SubscribeMessageWorker; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class SubscribeMessage { + + @SerializedName("a") + private String shard; + + @SerializedName("b") + private String subscriptionMatch; + + @SerializedName("c") + private String channel; + + @SerializedName("d") + private JsonElement payload; + + // TODO: figure me out + //@SerializedName("ear") + //private String payload; + + @SerializedName("f") + private String flags; + + @SerializedName("i") + private String issuingClientId; + + @SerializedName("k") + private String subscribeKey; + + //@SerializedName("s") + //private String sequenceNumber; + + @SerializedName("o") + private OriginationMetaData originationMetadata; + + @SerializedName("p") + private PublishMetaData publishMetaData; + + //@SerializedName("r") + //private Object replicationMap; + + @SerializedName("u") + private JsonElement userMetadata; + + //@SerializedName("w") + //private String waypointList; + + @SerializedName("e") + private Integer type; + + public boolean supportsEncryption() { + return type == null || type == SubscribeMessageWorker.TYPE_MESSAGE || type == SubscribeMessageWorker.TYPE_FILES; + } + +} diff --git a/src/main/java/com/pubnub/api/models/server/SubscribeMetadata.java b/src/main/java/com/pubnub/api/models/server/SubscribeMetadata.java new file mode 100644 index 000000000..237cdd7de --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/SubscribeMetadata.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +@Data +public class SubscribeMetadata { + + @SerializedName("t") + private final Long timetoken; + + @SerializedName("r") + private final String region; + +} diff --git a/src/main/java/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.java b/src/main/java/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.java new file mode 100644 index 000000000..6929f439b --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/access_manager/AccessManagerGrantPayload.java @@ -0,0 +1,37 @@ +package com.pubnub.api.models.server.access_manager; + +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeysData; +import lombok.Getter; + +import java.util.Map; + +@Getter +public class AccessManagerGrantPayload { + + @SerializedName("level") + private String level; + + private int ttl; + + @SerializedName("subscribe_key") + private String subscribeKey; + + @SerializedName("channels") + private Map channels; + + @SerializedName("channel-groups") + private JsonElement channelGroups; + + @SerializedName("uuids") + private Map uuids; + + @SerializedName("auths") + private Map authKeys; + + @SerializedName("channel") + private String channel; + +} diff --git a/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java b/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java new file mode 100644 index 000000000..b549bef63 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java @@ -0,0 +1,104 @@ +package com.pubnub.api.models.server.access_manager.v3; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.TokenBitmask; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNResource; +import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant; +import lombok.Builder; +import lombok.Data; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +public class GrantTokenRequestBody { + private final Integer ttl; + private final GrantTokenPermissions permissions; + + @Data + private static class GrantTokenPermissions { + private final GrantTokenPermission resources; + private final GrantTokenPermission patterns; + private final Object meta; + private final String uuid; + } + + @Data + public static class GrantTokenPermission { + private final Map channels; + private final Map groups; + private final Map uuids; + private final Map spaces = Collections.emptyMap(); + private final Map users = Collections.emptyMap(); + } + + @Builder + public static GrantTokenRequestBody of(Integer ttl, + List channels, + List groups, + List uuids, + Object meta, + String uuid) throws PubNubException { + + GrantTokenPermission resources = new GrantTokenPermission(getResources(channels), + getResources(groups), getResources(uuids)); + GrantTokenPermission patterns = new GrantTokenPermission(getPatterns(channels), + getPatterns(groups), getPatterns(uuids)); + GrantTokenPermissions permissions = new GrantTokenPermissions(resources, patterns, meta == null ? Collections.emptyMap() : meta, uuid); + return new GrantTokenRequestBody(ttl, permissions); + } + + private static > Map getResources(List resources) throws PubNubException { + final Map result = new HashMap<>(); + for (T resource : resources) { + if (!resource.isPatternResource()) { + result.put(resource.getId(), calculateBitmask(resource)); + } + } + return result; + } + + private static > Map getPatterns(List resources) throws PubNubException { + final Map result = new HashMap<>(); + for (T resource : resources) { + if (resource.isPatternResource()) { + result.put(resource.getId(), calculateBitmask(resource)); + } + } + return result; + + } + + private static int calculateBitmask(PNResource resource) throws PubNubException { + int sum = 0; + if (resource.isRead()) { + sum |= TokenBitmask.READ; + } + if (resource.isWrite()) { + sum |= TokenBitmask.WRITE; + } + if (resource.isManage()) { + sum |= TokenBitmask.MANAGE; + } + if (resource.isDelete()) { + sum |= TokenBitmask.DELETE; + } + if (resource.isCreate()) { + sum |= TokenBitmask.CREATE; + } + if (resource.isGet()) { + sum |= TokenBitmask.GET; + } + if (resource.isJoin()) { + sum |= TokenBitmask.JOIN; + } + if (resource.isUpdate()) { + sum |= TokenBitmask.UPDATE; + } + return sum; + } +} diff --git a/src/main/java/com/pubnub/api/models/server/files/FileUploadNotification.java b/src/main/java/com/pubnub/api/models/server/files/FileUploadNotification.java new file mode 100644 index 000000000..f7a2980cd --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/FileUploadNotification.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.server.files; + +import com.pubnub.api.models.consumer.files.PNBaseFile; +import lombok.Data; + +@Data +public class FileUploadNotification { + private final Object message; + private final PNBaseFile file; +} diff --git a/src/main/java/com/pubnub/api/models/server/files/FileUploadRequestDetails.java b/src/main/java/com/pubnub/api/models/server/files/FileUploadRequestDetails.java new file mode 100644 index 000000000..74c3e7046 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/FileUploadRequestDetails.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.server.files; + +import com.pubnub.api.models.consumer.files.PNFile; +import lombok.Data; + +import java.util.List; + +@Data +public class FileUploadRequestDetails { + private final Integer status; + private final PNFile data; + private final String url; + private final String method; + private final String expirationDate; + private final FormField keyFormField; + private final List formFields; +} diff --git a/src/main/java/com/pubnub/api/models/server/files/FormField.java b/src/main/java/com/pubnub/api/models/server/files/FormField.java new file mode 100644 index 000000000..645a2aa73 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/FormField.java @@ -0,0 +1,9 @@ +package com.pubnub.api.models.server.files; + +import lombok.Data; + +@Data +public class FormField { + private final String key; + private final String value; +} diff --git a/src/main/java/com/pubnub/api/models/server/files/GenerateUploadUrlPayload.java b/src/main/java/com/pubnub/api/models/server/files/GenerateUploadUrlPayload.java new file mode 100644 index 000000000..01e602e40 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/GenerateUploadUrlPayload.java @@ -0,0 +1,8 @@ +package com.pubnub.api.models.server.files; + +import lombok.Data; + +@Data +public class GenerateUploadUrlPayload { + private final String name; +} diff --git a/src/main/java/com/pubnub/api/models/server/files/GeneratedUploadUrlResponse.java b/src/main/java/com/pubnub/api/models/server/files/GeneratedUploadUrlResponse.java new file mode 100644 index 000000000..b38b1050e --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/GeneratedUploadUrlResponse.java @@ -0,0 +1,25 @@ +package com.pubnub.api.models.server.files; + +import com.google.gson.annotations.SerializedName; +import com.pubnub.api.models.consumer.files.PNUploadedFile; +import lombok.Data; + +import java.util.List; + +@Data +public class GeneratedUploadUrlResponse { + private final Integer status; + private final PNUploadedFile data; + @SerializedName("file_upload_request") + private final FileUploadRequest fileUploadRequest; + + @Data + public static class FileUploadRequest { + private final String url; + private final String method; + @SerializedName("expiration_date") + private final String expirationDate; + @SerializedName("form_fields") + private final List formFields; + } +} diff --git a/src/main/java/com/pubnub/api/models/server/files/ListFilesResult.java b/src/main/java/com/pubnub/api/models/server/files/ListFilesResult.java new file mode 100644 index 000000000..7f0b482ac --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/files/ListFilesResult.java @@ -0,0 +1,18 @@ +package com.pubnub.api.models.server.files; + +import com.pubnub.api.models.consumer.files.PNUploadedFile; +import lombok.Data; +import lombok.NonNull; + +import java.util.Collection; + +@Data +public class ListFilesResult { + @NonNull + private final int count; + private final String next; + @NonNull + private final int status; + @NonNull + private final Collection data; +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.java b/src/main/java/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.java new file mode 100644 index 000000000..b1b3cf5be --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/EntityArrayEnvelope.java @@ -0,0 +1,24 @@ +package com.pubnub.api.models.server.objects_api; + +import com.pubnub.api.models.consumer.PNPage; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +public class EntityArrayEnvelope extends EntityEnvelope> { + + protected Integer totalCount; + protected String next; + protected String prev; + + public PNPage nextPage() { + return PNPage.next(next); + } + + public PNPage previousPage() { + return PNPage.previous(prev); + } +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/EntityEnvelope.java b/src/main/java/com/pubnub/api/models/server/objects_api/EntityEnvelope.java new file mode 100644 index 000000000..bfcf04d55 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/EntityEnvelope.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.server.objects_api; + +import lombok.Getter; + +@Getter +public class EntityEnvelope { + + protected int status; + + @Getter + protected T data; +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/PatchMemberPayload.java b/src/main/java/com/pubnub/api/models/server/objects_api/PatchMemberPayload.java new file mode 100644 index 000000000..5eb97b83d --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/PatchMemberPayload.java @@ -0,0 +1,17 @@ +package com.pubnub.api.models.server.objects_api; + +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.Collection; + +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public +class PatchMemberPayload { + private Collection set; + private Collection delete; +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/PatchMembershipPayload.java b/src/main/java/com/pubnub/api/models/server/objects_api/PatchMembershipPayload.java new file mode 100644 index 000000000..cf2e73a80 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/PatchMembershipPayload.java @@ -0,0 +1,16 @@ +package com.pubnub.api.models.server.objects_api; + +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.Collection; + +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public class PatchMembershipPayload { + private Collection set; + private Collection delete; +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/SetChannelMetadataPayload.java b/src/main/java/com/pubnub/api/models/server/objects_api/SetChannelMetadataPayload.java new file mode 100644 index 000000000..30da5d1e5 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/SetChannelMetadataPayload.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models.server.objects_api; + +import lombok.*; + +@Getter +@ToString +@AllArgsConstructor +public class SetChannelMetadataPayload { + private final String name; + private final String description; + private final Object custom; +} diff --git a/src/main/java/com/pubnub/api/models/server/objects_api/SetUUIDMetadataPayload.java b/src/main/java/com/pubnub/api/models/server/objects_api/SetUUIDMetadataPayload.java new file mode 100644 index 000000000..d3084aea5 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/objects_api/SetUUIDMetadataPayload.java @@ -0,0 +1,15 @@ +package com.pubnub.api.models.server.objects_api; + +import lombok.*; + +@Getter +@ToString +@AllArgsConstructor +public class SetUUIDMetadataPayload { + private final String name; + private final String email; + private final String externalId; + private final String profileUrl; + private final Object custom; + +} diff --git a/src/main/java/com/pubnub/api/models/server/presence/WhereNowPayload.java b/src/main/java/com/pubnub/api/models/server/presence/WhereNowPayload.java new file mode 100644 index 000000000..64490aa01 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/server/presence/WhereNowPayload.java @@ -0,0 +1,10 @@ +package com.pubnub.api.models.server.presence; + +import lombok.Data; + +import java.util.List; + +@Data +public class WhereNowPayload { + private List channels; +} diff --git a/src/main/java/com/pubnub/api/services/AccessManagerService.java b/src/main/java/com/pubnub/api/services/AccessManagerService.java new file mode 100644 index 000000000..0c7958365 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/AccessManagerService.java @@ -0,0 +1,25 @@ +package com.pubnub.api.services; + +import com.google.gson.JsonObject; +import com.pubnub.api.models.server.Envelope; +import com.pubnub.api.models.server.access_manager.AccessManagerGrantPayload; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + +public interface AccessManagerService { + + @GET("/v2/auth/grant/sub-key/{subKey}") + Call> grant(@Path("subKey") String subKey, + @QueryMap Map options); + + @POST("/v3/pam/{subKey}/grant") + Call grantToken(@Path("subKey") String subKey, + @Body Object body, + @QueryMap Map options); +} diff --git a/src/main/java/com/pubnub/api/services/ChannelGroupService.java b/src/main/java/com/pubnub/api/services/ChannelGroupService.java new file mode 100644 index 000000000..8d573238b --- /dev/null +++ b/src/main/java/com/pubnub/api/services/ChannelGroupService.java @@ -0,0 +1,37 @@ +package com.pubnub.api.services; + +import com.pubnub.api.models.server.Envelope; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + + +public interface ChannelGroupService { + @GET("v1/channel-registration/sub-key/{subKey}/channel-group") + Call> listAllChannelGroup(@Path("subKey") String subKey, + @QueryMap Map options); + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + Call> allChannelsChannelGroup(@Path("subKey") String subKey, + @Path("group") String group, + @QueryMap Map options); + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + Call addChannelChannelGroup(@Path("subKey") String subKey, + @Path("group") String group, + @QueryMap Map options); + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}") + Call removeChannel(@Path("subKey") String subKey, + @Path("group") String group, + @QueryMap Map options); + + @GET("v1/channel-registration/sub-key/{subKey}/channel-group/{group}/remove") + Call deleteChannelGroup(@Path("subKey") String subKey, + @Path("group") String group, + @QueryMap Map options); +} + diff --git a/src/main/java/com/pubnub/api/services/ChannelMetadataService.java b/src/main/java/com/pubnub/api/services/ChannelMetadataService.java new file mode 100644 index 000000000..84d6e092e --- /dev/null +++ b/src/main/java/com/pubnub/api/services/ChannelMetadataService.java @@ -0,0 +1,37 @@ +package com.pubnub.api.services; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.server.objects_api.SetChannelMetadataPayload; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.server.objects_api.PatchMemberPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.http.*; + +import java.util.Map; + +public interface ChannelMetadataService { + + + @GET("v2/objects/{subKey}/channels") + Call> getChannelMetadata(@Path("subKey") String subKey, @QueryMap(encoded = true) Map options); + + @GET("v2/objects/{subKey}/channels/{channel}") + Call> getChannelMetadata(@Path("subKey") String subKey, @Path("channel") String channel, @QueryMap(encoded = true) Map options); + + @PATCH("/v2/objects/{subKey}/channels/{channel}") + @Headers("Content-Type: application/json; charset=UTF-8") + Call> setChannelsMetadata(@Path("subKey") String subKey, @Path("channel") String channel, @Body SetChannelMetadataPayload setChannelMetadataPayload, @QueryMap(encoded = true) Map options); + + @DELETE("/v2/objects/{subKey}/channels/{channel}") + Call> deleteChannelMetadata(@Path("subKey") String subKey, @Path("channel") String channel, @QueryMap(encoded = true) Map options); + + //FIXME if you find we need a separate service for this + @GET("v2/objects/{subKey}/channels/{channel}/uuids") + Call> getMembers(@Path("subKey") String subKey, @Path("channel") String channel, @QueryMap(encoded = true) Map options); + + @PATCH("v2/objects/{subKey}/channels/{channel}/uuids") + Call> patchMembers(@Path("subKey") String subKey, @Path("channel") String channel, @Body PatchMemberPayload patchMemberPayload, @QueryMap(encoded = true) Map options); +} diff --git a/src/main/java/com/pubnub/api/services/FilesService.java b/src/main/java/com/pubnub/api/services/FilesService.java new file mode 100644 index 000000000..aba3dc566 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/FilesService.java @@ -0,0 +1,52 @@ +package com.pubnub.api.services; + +import com.pubnub.api.models.server.files.GenerateUploadUrlPayload; +import com.pubnub.api.models.server.files.GeneratedUploadUrlResponse; +import com.pubnub.api.models.server.files.ListFilesResult; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.List; +import java.util.Map; + +public interface FilesService { + String GET_FILE_URL = "/v1/files/{subKey}/channels/{channel}/files/{fileId}/{fileName}"; + + @POST("/v1/files/{subKey}/channels/{channel}/generate-upload-url") + Call generateUploadUrl(@Path("subKey") String subKey, + @Path("channel") String channel, + @Body GenerateUploadUrlPayload body, + @QueryMap(encoded = true) Map options); + + @GET("/v1/files/publish-file/{pubKey}/{subKey}/0/{channel}/0/{message}") + Call> notifyAboutFileUpload(@Path("pubKey") String pubKey, + @Path("subKey") String subKey, + @Path("channel") String channel, + @Path(value = "message", encoded = true) String message, + @QueryMap(encoded = true) Map options); + + @GET("/v1/files/{subKey}/channels/{channel}/files") + Call listFiles(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap(encoded = true) Map options); + + @GET(GET_FILE_URL) + Call downloadFile(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("fileId") String fileId, + @Path("fileName") String fileName, + @QueryMap(encoded = true) Map options); + + @DELETE("/v1/files/{subKey}/channels/{channel}/files/{fileId}/{fileName}") + Call deleteFile(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("fileId") String fileId, + @Path("fileName") String fileName, + @QueryMap(encoded = true) Map options); +} diff --git a/src/main/java/com/pubnub/api/services/HistoryService.java b/src/main/java/com/pubnub/api/services/HistoryService.java new file mode 100644 index 000000000..cc51fae22 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/HistoryService.java @@ -0,0 +1,41 @@ +package com.pubnub.api.services; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.server.DeleteMessagesEnvelope; +import com.pubnub.api.models.server.FetchMessagesEnvelope; +import retrofit2.Call; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + +public interface HistoryService { + + @GET("v2/history/sub-key/{subKey}/channel/{channel}") + Call fetchHistory(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap Map options); + + @DELETE("v3/history/sub-key/{subKey}/channel/{channels}") + Call deleteMessages(@Path("subKey") String subKey, + @Path("channels") String channels, + @QueryMap Map options); + + @GET("v3/history/sub-key/{subKey}/channel/{channels}") + Call fetchMessages(@Path("subKey") String subKey, + @Path("channels") String channels, + @QueryMap Map options); + + @GET("v3/history-with-actions/sub-key/{subKey}/channel/{channel}") + Call fetchMessagesWithActions(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap Map options); + + @GET("v3/history/sub-key/{subKey}/message-counts/{channels}") + Call fetchCount(@Path("subKey") String subKey, + @Path("channels") String channels, + @QueryMap Map options); + +} diff --git a/src/main/java/com/pubnub/api/services/MessageActionService.java b/src/main/java/com/pubnub/api/services/MessageActionService.java new file mode 100644 index 000000000..fd4c3523e --- /dev/null +++ b/src/main/java/com/pubnub/api/services/MessageActionService.java @@ -0,0 +1,39 @@ +package com.pubnub.api.services; + +import com.pubnub.api.models.consumer.message_actions.PNGetMessageActionsResult; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + +public interface MessageActionService { + + @POST("v1/message-actions/{subKey}/channel/{channel}/message/{messageTimetoken}") + @Headers("Content-Type: application/json; charset=UTF-8") + Call> addMessageAction(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("messageTimetoken") String messageTimetoken, + @Body Object body, + @QueryMap(encoded = true) Map options); + + @GET("v1/message-actions/{subKey}/channel/{channel}") + Call getMessageActions(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap(encoded = true) Map options); + + @DELETE("v1/message-actions/{subKey}/channel/{channel}/message/{messageTimetoken}/action/{actionTimetoken}") + Call deleteMessageAction(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("messageTimetoken") String messageTimetoken, + @Path("actionTimetoken") String actionTimetoken, + @QueryMap(encoded = true) Map options); + +} diff --git a/src/main/java/com/pubnub/api/services/PresenceService.java b/src/main/java/com/pubnub/api/services/PresenceService.java new file mode 100644 index 000000000..92901aeb8 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/PresenceService.java @@ -0,0 +1,51 @@ +package com.pubnub.api.services; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.server.Envelope; +import com.pubnub.api.models.server.presence.WhereNowPayload; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + +public interface PresenceService { + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/leave") + Call leave(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap Map options); + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/heartbeat") + Call heartbeat(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap(encoded = true) Map options); + + @GET("v2/presence/sub-key/{subKey}/uuid/{uuid}") + Call> whereNow(@Path("subKey") String subKey, + @Path("uuid") String uuid, + @QueryMap Map options); + + @GET("v2/presence/sub_key/{subKey}") + Call> globalHereNow(@Path("subKey") String subKey, + @QueryMap Map options); + + @GET("v2/presence/sub_key/{subKey}/channel/{channel}") + Call> hereNow(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap Map options); + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/uuid/{uuid}") + Call> getState(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("uuid") String uuid, + @QueryMap Map options); + + @GET("v2/presence/sub-key/{subKey}/channel/{channel}/uuid/{uuid}/data") + Call> setState(@Path("subKey") String subKey, + @Path("channel") String channel, + @Path("uuid") String uuid, + @QueryMap(encoded = true) Map options); + +} diff --git a/src/main/java/com/pubnub/api/services/PublishService.java b/src/main/java/com/pubnub/api/services/PublishService.java new file mode 100644 index 000000000..48d03d522 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/PublishService.java @@ -0,0 +1,28 @@ +package com.pubnub.api.services; + +import retrofit2.Call; +import retrofit2.http.*; + +import java.util.List; +import java.util.Map; + +public interface PublishService { + + + @GET("publish/{pubKey}/{subKey}/0/{channel}/0/{message}") + Call> publish(@Path("pubKey") String pubKey, + @Path("subKey") String subKey, + @Path("channel") String channel, + @Path(value = "message", encoded = true) String message, + @QueryMap(encoded = true) Map options); + + @POST("publish/{pubKey}/{subKey}/0/{channel}/0") + @Headers("Content-Type: application/json; charset=UTF-8") + Call> publishWithPost(@Path("pubKey") String pubKey, + @Path("subKey") String subKey, + @Path("channel") String channel, + @Body Object body, + @QueryMap(encoded = true) Map options); + + +} diff --git a/src/main/java/com/pubnub/api/services/PushService.java b/src/main/java/com/pubnub/api/services/PushService.java new file mode 100644 index 000000000..99f8229a9 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/PushService.java @@ -0,0 +1,46 @@ +package com.pubnub.api.services; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.List; +import java.util.Map; + +public interface PushService { + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}") + Call> modifyChannelsForDevice(@Path("subKey") String subKey, + @Path("pushToken") String pushToken, + @QueryMap Map options); + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}/remove") + Call> removeAllChannelsForDevice(@Path("subKey") String subKey, + @Path("pushToken") String pushToken, + @QueryMap Map options); + + @GET("v1/push/sub-key/{subKey}/devices/{pushToken}") + Call> listChannelsForDevice(@Path("subKey") String subKey, + @Path("pushToken") String pushToken, + @QueryMap Map options); + + // V2 (APNS2) + + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}") + Call> modifyChannelsForDeviceApns2(@Path("subKey") String subKey, + @Path("deviceApns2") String deviceApns2, + @QueryMap Map options); + + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}") + Call> listChannelsForDeviceApns2(@Path("subKey") String subKey, + @Path("deviceApns2") String deviceApns2, + @QueryMap Map options); + + @GET("v2/push/sub-key/{subKey}/devices-apns2/{deviceApns2}/remove") + Call> removeAllChannelsForDeviceApns2(@Path("subKey") String subKey, + @Path("deviceApns2") String deviceApns2, + @QueryMap Map options); + + +} diff --git a/src/main/java/com/pubnub/api/services/S3Service.java b/src/main/java/com/pubnub/api/services/S3Service.java new file mode 100644 index 000000000..fbc4e3268 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/S3Service.java @@ -0,0 +1,12 @@ +package com.pubnub.api.services; + +import okhttp3.MultipartBody; +import retrofit2.Call; +import retrofit2.http.*; + +public interface S3Service { + + @POST + Call upload(@Url String url, + @Body MultipartBody form); +} diff --git a/src/main/java/com/pubnub/api/services/SignalService.java b/src/main/java/com/pubnub/api/services/SignalService.java new file mode 100644 index 000000000..37e82e6b5 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/SignalService.java @@ -0,0 +1,20 @@ +package com.pubnub.api.services; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.List; +import java.util.Map; + +public interface SignalService { + + @GET("/signal/{pubKey}/{subKey}/0/{channel}/0/{payload}") + Call> signal(@Path("pubKey") String pubKey, + @Path("subKey") String subKey, + @Path("channel") String channel, + @Path(value = "payload", encoded = true) String message, + @QueryMap(encoded = true) Map options); + +} diff --git a/src/main/java/com/pubnub/api/services/SubscribeService.java b/src/main/java/com/pubnub/api/services/SubscribeService.java new file mode 100644 index 000000000..4228dea1e --- /dev/null +++ b/src/main/java/com/pubnub/api/services/SubscribeService.java @@ -0,0 +1,18 @@ +package com.pubnub.api.services; + +import com.pubnub.api.models.server.SubscribeEnvelope; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +import java.util.Map; + +public interface SubscribeService { + + @GET("v2/subscribe/{subKey}/{channel}/0") + Call subscribe(@Path("subKey") String subKey, + @Path("channel") String channel, + @QueryMap(encoded = true) Map options); + +} diff --git a/src/main/java/com/pubnub/api/services/TimeService.java b/src/main/java/com/pubnub/api/services/TimeService.java new file mode 100644 index 000000000..1fea425c7 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/TimeService.java @@ -0,0 +1,13 @@ +package com.pubnub.api.services; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.QueryMap; + +import java.util.List; +import java.util.Map; + +public interface TimeService { + @GET("/time/0") + Call> fetchTime(@QueryMap Map options); +} diff --git a/src/main/java/com/pubnub/api/services/UUIDMetadataService.java b/src/main/java/com/pubnub/api/services/UUIDMetadataService.java new file mode 100644 index 000000000..927707fd7 --- /dev/null +++ b/src/main/java/com/pubnub/api/services/UUIDMetadataService.java @@ -0,0 +1,38 @@ +package com.pubnub.api.services; + +import com.google.gson.JsonElement; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.server.objects_api.PatchMembershipPayload; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.server.objects_api.SetUUIDMetadataPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import com.pubnub.api.models.server.objects_api.EntityEnvelope; +import retrofit2.Call; +import retrofit2.http.*; + +import java.util.Map; + + +public interface UUIDMetadataService { + + + @GET("v2/objects/{subKey}/uuids") + Call> getUUIDMetadata(@Path("subKey") String subKey, @QueryMap(encoded = true) Map options); + + @GET("v2/objects/{subKey}/uuids/{uuid}") + Call> getUUIDMetadata(@Path("subKey") String subKey, @Path("uuid") String uuid, @QueryMap(encoded = true) Map options); + + @PATCH("/v2/objects/{subKey}/uuids/{uuid}") + @Headers("Content-Type: application/json; charset=UTF-8") + Call> setUUIDsMetadata(@Path("subKey") String subKey, @Path("uuid") String uuid, @Body SetUUIDMetadataPayload setUUIDMetadataPayload, @QueryMap(encoded = true) Map options); + + @DELETE("/v2/objects/{subKey}/uuids/{uuid}") + Call> deleteUUIDMetadata(@Path("subKey") String subKey, @Path("uuid") String uuid, @QueryMap(encoded = true) Map options); + + //FIXME if you find we need a separate service for this + @GET("v2/objects/{subKey}/uuids/{uuid}/channels") + Call> getMemberships(@Path("subKey") String subKey, @Path("uuid") String uuid, @QueryMap(encoded = true) Map options); + + @PATCH("v2/objects/{subKey}/uuids/{uuid}/channels") + Call> patchMembership(@Path("subKey") String subKey, @Path("uuid") String uuid, @Body PatchMembershipPayload patchMembershipPayload, @QueryMap(encoded = true) Map options); +} diff --git a/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java b/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java new file mode 100644 index 000000000..5f26177b5 --- /dev/null +++ b/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java @@ -0,0 +1,22 @@ +package com.pubnub.api.utils; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +public class UnwrapSingleField implements JsonDeserializer { + @Override + public T deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + if (jsonObject.keySet().size() != 1) { + throw new IllegalStateException("Couldn't unwrap field for object containing more than 1 field. Actual number of fields: " + jsonObject.keySet().size()); + } + final String key = jsonObject.keySet().toArray(new String[]{})[0]; + final JsonElement element = jsonObject.get(key); + return context.deserialize(element, typeOfT); + } +} diff --git a/src/main/java/com/pubnub/api/vendor/Base64.java b/src/main/java/com/pubnub/api/vendor/Base64.java new file mode 100644 index 000000000..901562636 --- /dev/null +++ b/src/main/java/com/pubnub/api/vendor/Base64.java @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pubnub.api.vendor; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(Charset.forName("UTF-8")), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Non-data values in the DECODE arrays. + */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder(int flags, byte[] output) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3 / 4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p + 4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p + 1] & 0xff] << 12) | + (alphabet[input[p + 2] & 0xff] << 6) | + (alphabet[input[p + 3] & 0xff]))) >= 0) { + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op + 1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: + break; + case 1: + output_len += 2; + break; + case 2: + output_len += 3; + break; + default: + break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder(int flags, byte[] output) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8 / 5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p + 2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + case 2: + if (p + 1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p + 3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p + 1] & 0xff) << 8) | + (input[p + 2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op + 1] = alphabet[(v >> 12) & 0x3f]; + output[op + 2] = alphabet[(v >> 6) & 0x3f]; + output[op + 3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p - tailLen == len - 1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p - tailLen == len - 2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len - 1) { + tail[tailLen++] = input[p]; + } else if (p == len - 2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p + 1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { + } // don't instantiate +} diff --git a/src/main/java/com/pubnub/api/vendor/Crypto.java b/src/main/java/com/pubnub/api/vendor/Crypto.java new file mode 100644 index 000000000..accbeb1ef --- /dev/null +++ b/src/main/java/com/pubnub/api/vendor/Crypto.java @@ -0,0 +1,219 @@ +package com.pubnub.api.vendor; + +import com.pubnub.api.PubNubError; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import lombok.extern.slf4j.Slf4j; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Random; + +import static com.pubnub.api.vendor.FileEncryptionUtil.CIPHER_TRANSFORMATION; +import static com.pubnub.api.vendor.FileEncryptionUtil.ENCODING_UTF_8; + + +@Slf4j +public class Crypto { + + byte[] keyBytes = null; + byte[] ivBytes = null; + String initializationVector = "0123456789012345"; + String cipherKey; + boolean INIT = false; + boolean dynamicIV = false; + + public Crypto(String cipherKey) { + this(cipherKey, false); + } + + public Crypto(String cipherKey, boolean dynamicIV) { + this.cipherKey = cipherKey; + this.dynamicIV = dynamicIV; + } + + public Crypto(String cipherKey, String customInitializationVector) { + if (customInitializationVector != null) { + this.initializationVector = customInitializationVector; + } + + this.cipherKey = cipherKey; + } + + private void initCiphers() throws PubNubException { + if (INIT && !dynamicIV) + return; + try { + + keyBytes = new String(hexEncode(sha256(this.cipherKey.getBytes(ENCODING_UTF_8))), ENCODING_UTF_8) + .substring(0, 32) + .toLowerCase().getBytes(ENCODING_UTF_8); + if (dynamicIV){ + ivBytes = new byte[16]; + new Random().nextBytes(ivBytes); + } + else { + ivBytes = initializationVector.getBytes(ENCODING_UTF_8); + INIT = true; + } + } catch (UnsupportedEncodingException e) { + throw PubNubException.builder().pubnubError(newCryptoError(11, e.toString())).errormsg(e.getMessage()).cause(e).build(); + } + } + + public static byte[] hexEncode(byte[] input) throws PubNubException { + StringBuffer result = new StringBuffer(); + for (byte byt : input) + result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1)); + try { + return result.toString().getBytes(ENCODING_UTF_8); + } catch (UnsupportedEncodingException e) { + throw PubNubException.builder().pubnubError(newCryptoError(12, e.toString())).errormsg(e.getMessage()).cause(e).build(); + } + } + + private static PubNubError newCryptoError(int code, String message) { + + return PubNubErrorBuilder.createCryptoError(code, message); + } + + public String encrypt(String input) throws PubNubException { + try { + initCiphers(); + AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); + SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); + Cipher cipher = null; + cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec); + if (dynamicIV) { + byte[] encrypted = cipher.doFinal(input.getBytes(ENCODING_UTF_8)); + byte[] encryptedWithIV = new byte[ivBytes.length + encrypted.length]; + System.arraycopy(ivBytes, 0, encryptedWithIV, 0, ivBytes.length); + System.arraycopy(encrypted, 0, encryptedWithIV, ivBytes.length, encrypted.length); + return new String(Base64.encode(encryptedWithIV, 0), Charset.forName(ENCODING_UTF_8)); + } + else { + return new String(Base64.encode(cipher.doFinal(input.getBytes(ENCODING_UTF_8)), 0), Charset.forName(ENCODING_UTF_8)); + } + } catch (NoSuchAlgorithmException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (NoSuchPaddingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (InvalidKeyException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (InvalidAlgorithmParameterException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (UnsupportedEncodingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (IllegalBlockSizeException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (BadPaddingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } + + } + + /** + * Decrypt + * + * @param cipher_text + * @return String + * @throws PubNubException + */ + public String decrypt(String cipher_text) throws PubNubException { + try { + byte[] dataBytes = null; + initCiphers(); + if (dynamicIV){ + dataBytes = Base64.decode(cipher_text, 0); + System.arraycopy(dataBytes, 0, ivBytes, 0, 16); + byte[] receivedCipherBytes = new byte[dataBytes.length - 16]; + System.arraycopy(dataBytes, 16, receivedCipherBytes, 0, dataBytes.length-16); + dataBytes = receivedCipherBytes; + } + else { + dataBytes = Base64.decode(cipher_text, 0); + } + AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); + SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); + Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec); + return new String(cipher.doFinal(dataBytes), ENCODING_UTF_8); + } catch (IllegalArgumentException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (UnsupportedEncodingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (IllegalBlockSizeException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (BadPaddingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (InvalidKeyException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (InvalidAlgorithmParameterException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (NoSuchAlgorithmException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } catch (NoSuchPaddingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + /** + * Get MD5 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + public static byte[] md5(String input) throws PubNubException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + byte[] hashedBytes = digest.digest(input.getBytes(ENCODING_UTF_8)); + return hashedBytes; + } catch (NoSuchAlgorithmException e) { + throw PubNubException.builder().pubnubError(newCryptoError(118, e.toString())).errormsg(e.getMessage()).cause(e).build(); + } catch (UnsupportedEncodingException e) { + throw PubNubException.builder().pubnubError(newCryptoError(119, e.toString())).errormsg(e.getMessage()).cause(e).build(); + } + } + + /** + * Get SHA256 + * + * @param input + * @return byte[] + * @throws PubNubException + */ + public static byte[] sha256(byte[] input) throws PubNubException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + byte[] hashedBytes = digest.digest(input); + return hashedBytes; + } catch (NoSuchAlgorithmException e) { + throw PubNubException.builder().pubnubError(newCryptoError(111, e.toString())).errormsg(e.getMessage()).cause(e).build(); + } + } + +} diff --git a/src/main/java/com/pubnub/api/vendor/FileEncryptionUtil.java b/src/main/java/com/pubnub/api/vendor/FileEncryptionUtil.java new file mode 100644 index 000000000..1c4ff005f --- /dev/null +++ b/src/main/java/com/pubnub/api/vendor/FileEncryptionUtil.java @@ -0,0 +1,143 @@ +package com.pubnub.api.vendor; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import lombok.Data; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import static com.pubnub.api.PubNubUtil.readBytes; +import static com.pubnub.api.vendor.Crypto.hexEncode; +import static com.pubnub.api.vendor.Crypto.sha256; + +public final class FileEncryptionUtil { + private static final int IV_SIZE_BYTES = 16; + public static final int BUFFER_SIZE_BYTES = 8192; + static final String ENCODING_UTF_8 = "UTF-8"; + static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding"; + + @Data + private static class IvAndData { + final byte[] ivBytes; + final byte[] dataToDecrypt; + } + + private FileEncryptionUtil() {} + + public static String effectiveCipherKey(PubNub pubNub, String cipherKey) { + if (cipherKey != null) { + return cipherKey; + } else if (pubNub.getConfiguration().getCipherKey() != null) { + return pubNub.getConfiguration().getCipherKey(); + } else { + return null; + } + } + + public static byte[] encryptToBytes(final String cipherKey, final byte[] bytesToEncrypt) + throws PubNubException { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + final byte[] keyBytes = keyBytes(cipherKey); + final byte[] randomIvBytes = randomIv(); + final Cipher encryptionCipher = encryptionCipher(keyBytes, randomIvBytes); + + byteArrayOutputStream.write(randomIvBytes); + byteArrayOutputStream.write(encryptionCipher.doFinal(bytesToEncrypt)); + return byteArrayOutputStream.toByteArray(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException | + InvalidKeyException | IOException | BadPaddingException | IllegalBlockSizeException e) { + throw PubNubException.builder().errormsg(e.toString()).build(); + } + } + + public static InputStream encrypt(final String cipherKey, final InputStream inputStreamToEncrypt) + throws PubNubException { + + try { + return new ByteArrayInputStream(encryptToBytes(cipherKey, readBytes(inputStreamToEncrypt))); + } catch (IOException e) { + throw PubNubException.builder() + .errormsg(e.getMessage()) + .cause(e) + .build(); + } + } + + public static InputStream decrypt(final String cipherKey, final InputStream encryptedInputStream) + throws PubNubException { + try { + final byte[] keyBytes = keyBytes(cipherKey); + final IvAndData ivAndData = loadIvAndDataFromInputStream(encryptedInputStream); + final Cipher decryptionCipher = decryptionCipher(keyBytes, ivAndData.ivBytes); + byte[] decryptedBytes = decryptionCipher.doFinal(ivAndData.dataToDecrypt); + return new ByteArrayInputStream(decryptedBytes); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException + | InvalidKeyException | IOException | IllegalBlockSizeException | BadPaddingException e) { + throw PubNubException.builder().errormsg(e.toString()).cause(e).build(); + } + } + + private static IvAndData loadIvAndDataFromInputStream(final InputStream inputStreamToEncrypt) throws IOException { + final byte[] ivBytes = new byte[IV_SIZE_BYTES]; + { + int read; + int readSoFar = 0; + do { + read = inputStreamToEncrypt.read(ivBytes, readSoFar, IV_SIZE_BYTES - readSoFar); + if (read != -1) { + readSoFar += read; + } + } while (read != -1 && readSoFar < IV_SIZE_BYTES); + if (read == -1) { + throw new IOException("EOF before IV fully read"); + } + } + + return new IvAndData(ivBytes, readBytes(inputStreamToEncrypt)); + } + + private static Cipher encryptionCipher(final byte[] keyBytes, final byte[] ivBytes) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException { + return cipher(keyBytes, ivBytes, Cipher.ENCRYPT_MODE); + } + + private static Cipher decryptionCipher(final byte[] keyBytes, final byte[] ivBytes) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException { + return cipher(keyBytes, ivBytes, Cipher.DECRYPT_MODE); + } + + private static Cipher cipher(final byte[] keyBytes, final byte[] ivBytes, final int mode) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + InvalidAlgorithmParameterException { + Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); + AlgorithmParameterSpec iv = new IvParameterSpec(ivBytes); + SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); + cipher.init(mode, key, iv); + return cipher; + } + + private static byte[] keyBytes(final String cipherKey) throws UnsupportedEncodingException, PubNubException { + return new String(hexEncode(sha256(cipherKey.getBytes(ENCODING_UTF_8))), ENCODING_UTF_8) + .substring(0, 32) + .toLowerCase().getBytes(ENCODING_UTF_8); + } + + private static byte[] randomIv() throws NoSuchAlgorithmException { + byte[] randomIv = new byte[IV_SIZE_BYTES]; + SecureRandom.getInstance("SHA1PRNG").nextBytes(randomIv); + return randomIv; + } +} diff --git a/src/main/java/com/pubnub/api/workers/SubscribeMessageWorker.java b/src/main/java/com/pubnub/api/workers/SubscribeMessageWorker.java new file mode 100644 index 000000000..65f74982f --- /dev/null +++ b/src/main/java/com/pubnub/api/workers/SubscribeMessageWorker.java @@ -0,0 +1,345 @@ +package com.pubnub.api.workers; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.managers.DuplicationManager; +import com.pubnub.api.managers.ListenerManager; +import com.pubnub.api.managers.MapperManager; +import com.pubnub.api.models.consumer.PNErrorData; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.files.PNDownloadableFile; +import com.pubnub.api.models.consumer.message_actions.PNMessageAction; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadata; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.BasePubSubResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import com.pubnub.api.models.consumer.pubsub.objects.ObjectPayload; +import com.pubnub.api.models.server.PresenceEnvelope; +import com.pubnub.api.models.server.PublishMetaData; +import com.pubnub.api.models.server.SubscribeMessage; +import com.pubnub.api.models.server.files.FileUploadNotification; +import com.pubnub.api.services.FilesService; +import com.pubnub.api.vendor.Crypto; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; + + +@Slf4j +public class SubscribeMessageWorker implements Runnable { + + public static final int TYPE_MESSAGE = 0; + private final int typeSignal = 1; + private final int typeObject = 2; + private final int typeMessageAction = 3; + public static final int TYPE_FILES = 4; + + private PubNub pubnub; + private ListenerManager listenerManager; + private LinkedBlockingQueue queue; + private DuplicationManager duplicationManager; + + public SubscribeMessageWorker(PubNub pubnubInstance, + ListenerManager listenerManagerInstance, + LinkedBlockingQueue queueInstance, + DuplicationManager dupManager) { + this.pubnub = pubnubInstance; + this.listenerManager = listenerManagerInstance; + this.queue = queueInstance; + this.duplicationManager = dupManager; + } + + @Override + public void run() { + takeMessage(); + } + + + private void takeMessage() { + while (!Thread.interrupted()) { + try { + this.processIncomingPayload(this.queue.take()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.trace("take message interrupted", e); + } + } + } + + private JsonElement processMessage(SubscribeMessage subscribeMessage) { + JsonElement input = subscribeMessage.getPayload(); + + // if we do not have a crypto key, there is no way to process the node; let's return. + if (pubnub.getConfiguration().getCipherKey() == null) { + return input; + } + + // if the message couldn't possibly be encrypted in the first place, there is no way to process the node; let's + // return. + if (!subscribeMessage.supportsEncryption()) { + return input; + } + + Crypto crypto = new Crypto(pubnub.getConfiguration().getCipherKey(), + pubnub.getConfiguration().isUseRandomInitializationVector()); + MapperManager mapper = this.pubnub.getMapper(); + String inputText; + String outputText; + JsonElement outputObject; + + if (mapper.isJsonObject(input) && mapper.hasField(input, "pn_other")) { + inputText = mapper.elementToString(input, "pn_other"); + } else { + inputText = mapper.elementToString(input); + } + + try { + outputText = crypto.decrypt(inputText); + } catch (PubNubException e) { + PNStatus pnStatus = PNStatus.builder().error(true) + .errorData(new PNErrorData(e.getMessage(), e)) + .operation(PNOperationType.PNSubscribeOperation) + .category(PNStatusCategory.PNDecryptionErrorCategory) + .build(); + + listenerManager.announce(pnStatus); + return null; + } + + try { + outputObject = mapper.fromJson(outputText, JsonElement.class); + } catch (PubNubException e) { + PNStatus pnStatus = PNStatus.builder().error(true) + .errorData(new PNErrorData(e.getMessage(), e)) + .operation(PNOperationType.PNSubscribeOperation) + .category(PNStatusCategory.PNMalformedResponseCategory) + .build(); + + listenerManager.announce(pnStatus); + return null; + } + + // inject the decoded response into the payload + if (mapper.isJsonObject(input) && mapper.hasField(input, "pn_other")) { + JsonObject objectNode = mapper.getAsObject(input); + mapper.putOnObject(objectNode, "pn_other", outputObject); + outputObject = objectNode; + } + + return outputObject; + } + + private void processIncomingPayload(SubscribeMessage message) { + MapperManager mapper = this.pubnub.getMapper(); + + String channel = message.getChannel(); + String subscriptionMatch = message.getSubscriptionMatch(); + PublishMetaData publishMetaData = message.getPublishMetaData(); + + if (channel != null && channel.equals(subscriptionMatch)) { + subscriptionMatch = null; + } + + if (this.pubnub.getConfiguration().isDedupOnSubscribe()) { + if (this.duplicationManager.isDuplicate(message)) { + return; + } else { + this.duplicationManager.addEntry(message); + } + } + + if (message.getChannel().endsWith("-pnpres")) { + PresenceEnvelope presencePayload = mapper.convertValue(message.getPayload(), PresenceEnvelope.class); + + String strippedPresenceChannel = null; + String strippedPresenceSubscription = null; + + if (channel != null) { + strippedPresenceChannel = PubNubUtil.replaceLast(channel, "-pnpres", ""); + } + if (subscriptionMatch != null) { + strippedPresenceSubscription = PubNubUtil.replaceLast(subscriptionMatch, "-pnpres", ""); + } + + JsonElement isHereNowRefresh = message.getPayload().getAsJsonObject().get("here_now_refresh"); + + PNPresenceEventResult pnPresenceEventResult = PNPresenceEventResult.builder() + .event(presencePayload.getAction()) + // deprecated + .actualChannel((subscriptionMatch != null) ? channel : null) + .subscribedChannel(subscriptionMatch != null ? subscriptionMatch : channel) + // deprecated + .channel(strippedPresenceChannel) + .subscription(strippedPresenceSubscription) + .state(presencePayload.getData()) + .timetoken(publishMetaData.getPublishTimetoken()) + .occupancy(presencePayload.getOccupancy()) + .uuid(presencePayload.getUuid()) + .timestamp(presencePayload.getTimestamp()) + .join(getDelta(message.getPayload().getAsJsonObject().get("join"))) + .leave(getDelta(message.getPayload().getAsJsonObject().get("leave"))) + .timeout(getDelta(message.getPayload().getAsJsonObject().get("timeout"))) + .hereNowRefresh(isHereNowRefresh != null && isHereNowRefresh.getAsBoolean()) + .build(); + + listenerManager.announce(pnPresenceEventResult); + } else { + JsonElement extractedMessage = processMessage(message); + + if (extractedMessage == null) { + log.debug("unable to parse payload on #processIncomingMessages"); + } + + BasePubSubResult result = BasePubSubResult.builder() + // deprecated + .actualChannel((subscriptionMatch != null) ? channel : null) + .subscribedChannel(subscriptionMatch != null ? subscriptionMatch : channel) + // deprecated + .channel(channel) + .subscription(subscriptionMatch) + .timetoken(publishMetaData.getPublishTimetoken()) + .publisher(message.getIssuingClientId()) + .userMetadata(message.getUserMetadata()) + .build(); + + if (message.getType() == null) { + listenerManager.announce(new PNMessageResult(result, extractedMessage)); + } else if (message.getType() == TYPE_MESSAGE) { + listenerManager.announce(new PNMessageResult(result, extractedMessage)); + } else if (message.getType() == typeSignal) { + listenerManager.announce(new PNSignalResult(result, extractedMessage)); + } else if (message.getType() == typeObject) { + ObjectPayload objectPayload = mapper.convertValue(extractedMessage, ObjectPayload.class); + String type = objectPayload.getType(); + if (canHandleObjectCallback(objectPayload)) { + switch (type) { + case "channel": + final PNChannelMetadataResult channelMetadataResult = new PNChannelMetadataResult(result, + objectPayload.getEvent(), mapper.convertValue(objectPayload.getData(), + PNChannelMetadata.class)); + listenerManager.announce(channelMetadataResult); + break; + case "membership": + final PNMembershipResult membershipResult = new PNMembershipResult(result, + objectPayload.getEvent(), mapper.convertValue(objectPayload.getData(), + PNMembership.class)); + listenerManager.announce(membershipResult); + break; + case "uuid": + final PNUUIDMetadataResult uuidMetadataResult = new PNUUIDMetadataResult(result, + objectPayload.getEvent(), + mapper.convertValue(objectPayload.getData(), PNUUIDMetadata.class)); + listenerManager.announce(uuidMetadataResult); + break; + default: + } + } + } else if (message.getType() == typeMessageAction) { + ObjectPayload objectPayload = mapper.convertValue(extractedMessage, ObjectPayload.class); + JsonObject data = objectPayload.getData().getAsJsonObject(); + if (!data.has("uuid")) { + data.addProperty("uuid", result.getPublisher()); + } + listenerManager.announce(PNMessageActionResult.actionBuilder() + .result(result) + .event(objectPayload.getEvent()) + .data(mapper.convertValue(data, PNMessageAction.class)) + .build()); + } else if (message.getType() == TYPE_FILES) { + FileUploadNotification event = mapper.convertValue(extractedMessage, FileUploadNotification.class); + listenerManager.announce(PNFileEventResult.builder() + .file(new PNDownloadableFile(event.getFile().getId(), + event.getFile().getName(), + buildFileUrl(message.getChannel(), + event.getFile().getId(), + event.getFile().getName()))) + .message(event.getMessage()) + .channel(message.getChannel()) + .publisher(message.getIssuingClientId()) + .timetoken(publishMetaData.getPublishTimetoken()) + .build()); + } + + } + } + + @SuppressWarnings("RegExpRedundantEscape") + private final String formatFriendlyGetFileUrl = "%s" + FilesService.GET_FILE_URL.replaceAll("\\{.*?\\}", "%s"); + + private String buildFileUrl(String channel, String fileId, String fileName) { + String basePath = String.format(formatFriendlyGetFileUrl, + pubnub.getBaseUrl(), + pubnub.getConfiguration().getSubscribeKey(), + channel, + fileId, + fileName); + + ArrayList queryParams = new ArrayList<>(); + String authKey = pubnub.getConfiguration().getAuthKey(); + + if (PubNubUtil.shouldSignRequest(pubnub.getConfiguration())) { + int timestamp = pubnub.getTimestamp(); + String signature = generateSignature(pubnub.getConfiguration(), basePath, authKey, timestamp); + queryParams.add(PubNubUtil.TIMESTAMP_QUERY_PARAM_NAME + "=" + timestamp); + queryParams.add(PubNubUtil.SIGNATURE_QUERY_PARAM_NAME + "=" + signature); + } + + if (authKey != null) { + queryParams.add(PubNubUtil.AUTH_QUERY_PARAM_NAME + "=" + authKey); + } + + if (queryParams.isEmpty()) { + return basePath; + } else { + return basePath + "?" + PubNubUtil.joinString(queryParams, "&"); + } + } + + private String generateSignature(PNConfiguration configuration, String url, String authKey, int timestamp) { + HashMap queryParams = new HashMap<>(); + if (authKey != null) { + queryParams.put("auth", authKey); + } + return PubNubUtil.generateSignature(configuration, + url, + queryParams, + "get", + null, + timestamp + ); + } + + private boolean canHandleObjectCallback(final ObjectPayload objectPayload) { + return objectPayload.getVersion().equals("2.0"); + } + + private List getDelta(JsonElement delta) { + List list = new ArrayList<>(); + if (delta != null) { + JsonArray jsonArray = delta.getAsJsonArray(); + for (int i = 0; i < jsonArray.size(); i++) { + list.add(jsonArray.get(i).getAsString()); + } + } + + return list; + } +} diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties new file mode 100644 index 000000000..308c9f8ec --- /dev/null +++ b/src/main/resources/version.properties @@ -0,0 +1 @@ +version=${projectVersion} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/Base64Test.java b/src/test/java/com/pubnub/api/Base64Test.java new file mode 100644 index 000000000..d33c821c7 --- /dev/null +++ b/src/test/java/com/pubnub/api/Base64Test.java @@ -0,0 +1,17 @@ +package com.pubnub.api; + +import com.pubnub.api.vendor.Base64; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.Charset; + +public class Base64Test { + + + @Test + public void testBase64Encode() { + Assert.assertEquals("YWJj", Base64.encodeToString("abc".getBytes(Charset.forName("UTF-8")), 0).trim()); + } + +} diff --git a/src/test/java/com/pubnub/api/PubNubExceptionTest.java b/src/test/java/com/pubnub/api/PubNubExceptionTest.java new file mode 100644 index 000000000..59f917b23 --- /dev/null +++ b/src/test/java/com/pubnub/api/PubNubExceptionTest.java @@ -0,0 +1,53 @@ +package com.pubnub.api; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.endpoints.pubsub.Publish; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + + +public class PubNubExceptionTest extends TestHarness { + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private Publish instance; + + @Before + public void beforeEach() throws IOException { + PubNub pubnub = this.createPubNubInstance(); + instance = pubnub.publish(); + wireMockRule.start(); + } + + @After + public void cleanup() { + instance = null; + wireMockRule.stop(); + } + + @Test + public void testPubnubError() { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withStatus(404).withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + int statusCode = -1; + + try { + instance.channel("coolChannel").message("hi").sync(); + } catch (PubNubException error) { + statusCode = error.getStatusCode(); + } + + assertEquals(404, statusCode); + } + +} diff --git a/src/test/java/com/pubnub/api/PubNubTest.java b/src/test/java/com/pubnub/api/PubNubTest.java new file mode 100644 index 000000000..501b4977a --- /dev/null +++ b/src/test/java/com/pubnub/api/PubNubTest.java @@ -0,0 +1,117 @@ +package com.pubnub.api; + +import com.pubnub.api.enums.PNReconnectionPolicy; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class PubNubTest { + private PubNub pubnub; + private PNConfiguration pnConfiguration; + + @Before + public void beforeEach() throws IOException { + pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey("demo"); + pnConfiguration.setPublishKey("demo"); + pnConfiguration.setUseRandomInitializationVector(false); + } + + @After + public void cleanup() { + pubnub.forceDestroy(); + pubnub = null; + } + + @Test + public void testCreateSuccess() { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals(true, pubnub.getConfiguration().isSecure()); + Assert.assertNotNull("pubnub object is null", pubnub); + Assert.assertNotNull(pubnub.getConfiguration()); + Assert.assertEquals("https://ps.pndsn.com", pubnub.getBaseUrl()); + } + + @Test + public void testEncryptCustomKey() throws PubNubException { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("iALQtn3PfIXe74CT/wrS7g==", pubnub.encrypt("test1", "cipherKey").trim()); + + } + + @Test + public void testEncryptConfigurationKey() throws PubNubException { + pnConfiguration.setCipherKey("cipherKey"); + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("iALQtn3PfIXe74CT/wrS7g==", pubnub.encrypt("test1").trim()); + + } + + @Test + public void testDecryptCustomKey() throws PubNubException { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.decrypt("iALQtn3PfIXe74CT/wrS7g==", "cipherKey").trim()); + + } + + @Test + public void testDecryptConfigurationKey() throws PubNubException { + pnConfiguration.setCipherKey("cipherKey"); + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.decrypt("iALQtn3PfIXe74CT/wrS7g==").trim()); + + } + + @Test + public void testPNConfiguration() { + pnConfiguration.setSubscribeTimeout(3000); + pnConfiguration.setConnectTimeout(4000); + pnConfiguration.setNonSubscribeRequestTimeout(5000); + pnConfiguration.setReconnectionPolicy(PNReconnectionPolicy.NONE); + pubnub = new PubNub(pnConfiguration); + + Assert.assertNotNull("pubnub object is null", pubnub); + Assert.assertNotNull(pubnub.getConfiguration()); + Assert.assertEquals("https://ps.pndsn.com", pubnub.getBaseUrl()); + Assert.assertEquals(3000, pnConfiguration.getSubscribeTimeout()); + Assert.assertEquals(4000, pnConfiguration.getConnectTimeout()); + Assert.assertEquals(5000, pnConfiguration.getNonSubscribeRequestTimeout()); + } + + @Test(expected = PubNubException.class) + public void testDecryptNull() throws PubNubException { + pnConfiguration.setCipherKey("cipherKey"); + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.decrypt(null).trim()); + } + + @Test(expected = PubNubException.class) + public void testDecryptNull_B() throws PubNubException { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.decrypt(null, "cipherKey").trim()); + } + + @Test + public void getVersionAndTimeStamp() { + pubnub = new PubNub(pnConfiguration); + String version = pubnub.getVersion(); + int timeStamp = pubnub.getTimestamp(); + Assert.assertEquals("5.2.1", version); + Assert.assertTrue(timeStamp > 0); + } + + @Test(expected = PubNubException.class) + public void testEcryptNull() throws PubNubException { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.encrypt(null)); + } + + @Test(expected = PubNubException.class) + public void testEcryptNull_B() throws PubNubException { + pubnub = new PubNub(pnConfiguration); + Assert.assertEquals("test1", pubnub.encrypt(null, "chiperKey")); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/DeleteMessagesEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/DeleteMessagesEndpointTest.java new file mode 100644 index 000000000..4a24f8b75 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/DeleteMessagesEndpointTest.java @@ -0,0 +1,78 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.history.PNDeleteMessagesResult; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertNotNull; + + +public class DeleteMessagesEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private DeleteMessages partialHistory; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialHistory = pubnub.deleteMessages(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws PubNubException { + stubFor(delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": False, \"error_message\": \"\"}"))); + + PNDeleteMessagesResult response = partialHistory.channels(Arrays.asList("mychannel,my_channel")).sync(); + + assertNotNull(response); + } + + @Test + public void testSyncAuthSuccess() throws PubNubException { + stubFor(delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": False, \"error_message\": \"\"}"))); + + pubnub.getConfiguration().setAuthKey("authKey"); + + PNDeleteMessagesResult response = partialHistory.channels(Arrays.asList("mychannel,my_channel")).sync(); + + assertNotNull(response); + } + + @Test + public void testFailure() throws PubNubException { + stubFor(delete(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 403, \"error\": False, \"error_message\": \"wut\"}"))); + + pubnub.getConfiguration().setAuthKey("authKey"); + + try { + partialHistory.channels(Arrays.asList("mychannel,my_channel")).sync(); + } catch (PubNubException ex) { + assert (ex.getErrormsg().equals("wut")); + } + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/EndpointTest.java b/src/test/java/com/pubnub/api/endpoints/EndpointTest.java new file mode 100644 index 000000000..f2450f429 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/EndpointTest.java @@ -0,0 +1,202 @@ +package com.pubnub.api.endpoints; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.token_manager.TokenManager; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.ResponseBody; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; + +public class EndpointTest extends TestHarness { + + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + pubnub.getConfiguration().setIncludeInstanceIdentifier(true); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + } + + @Test + public void testBaseParams() throws PubNubException { + Endpoint endpoint = new Endpoint(pubnub, null, null, new TokenManager()) { + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + } + + @Override + protected Object createResponse(Response input) throws PubNubException { + return null; + } + + @Override + protected PNOperationType getOperationType() { + return null; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + + Call fakeCall = successfulCall(); + + Assert.assertEquals("myUUID", baseParams.get("uuid")); + Assert.assertEquals("PubNubRequestId", baseParams.get("requestid")); + Assert.assertEquals("PubNubInstanceId", baseParams.get("instanceid")); + return fakeCall; + } + }; + + endpoint.sync(); + } + + @Test + public void payloadTooLargeTest_Sync() { + Endpoint endpoint = testEndpoint(call(Response.error(HttpURLConnection.HTTP_ENTITY_TOO_LARGE, + ResponseBody.create(MediaType.get("application/json"), "{}")))); + + try { + endpoint.sync(); + Assert.fail("Exception expected"); + } catch (PubNubException e) { + Assert.assertEquals(PubNubErrorBuilder.PNERR_PAYLOAD_TOO_LARGE, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void payloadTooLargeTest_Async() { + Endpoint endpoint = testEndpoint(call(Response.error(HttpURLConnection.HTTP_ENTITY_TOO_LARGE, + ResponseBody.create(MediaType.get("application/json"), "{}")))); + + endpoint.async((result, status) -> { + if (status.isError()) { + Assert.assertEquals(PubNubErrorBuilder.PNERR_PAYLOAD_TOO_LARGE, status.getStatusCode()); + } else { + Assert.fail("Error expected"); + } + }); + } + + private Endpoint testEndpoint(Call call) { + return new Endpoint(pubnub, null, null, new TokenManager()) { + + @Override + protected List getAffectedChannels() { + return null; + } + + @Override + protected List getAffectedChannelGroups() { + return null; + } + + @Override + protected void validateParams() throws PubNubException { + } + + @Override + protected Object createResponse(Response input) throws PubNubException { + return null; + } + + @Override + protected PNOperationType getOperationType() { + return null; + } + + @Override + protected boolean isAuthRequired() { + return true; + } + + @Override + protected Call doWork(Map baseParams) throws PubNubException { + + Call fakeCall = call; + return fakeCall; + } + }; + } + + private Call call(Response response) { + return new Call() { + + @Override + public Response execute() throws IOException { + return response; + } + + @Override + public void enqueue(Callback callback) { + Call that = this; + Executors.newSingleThreadExecutor().execute( + () -> callback.onResponse(that, response) + ); + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call clone() { + return this; + } + + @Override + public Request request() { + return new Request.Builder().build(); + } + }; + } + + private Call successfulCall() { + return call(Response.success(null)); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/FetchMessagesEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/FetchMessagesEndpointTest.java new file mode 100644 index 000000000..90f49f13a --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/FetchMessagesEndpointTest.java @@ -0,0 +1,110 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.history.PNFetchMessagesResult; +import org.junit.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + + +public class FetchMessagesEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(PORT), false); + + private FetchMessages partialHistory; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialHistory = pubnub.fetchMessages(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws PubNubException { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":[{\"message\":\"hihi\",\"timetoken\":\"14698320467224036\"}," + + "{\"message\":\"Hey\",\"timetoken\":\"14698320468265639\"}]," + + "\"mychannel\":[{\"message\":\"sample message\",\"timetoken\":\"14369823849575729\"}]}}"))); + + PNFetchMessagesResult response = + partialHistory.channels(Arrays.asList("mychannel,my_channel")).maximumPerChannel(25).sync(); + + assert response != null; + + Assert.assertEquals(response.getChannels().size(), 2); + Assert.assertTrue(response.getChannels().containsKey("mychannel")); + Assert.assertTrue(response.getChannels().containsKey("my_channel")); + Assert.assertEquals(response.getChannels().get("mychannel").size(), 1); + Assert.assertEquals(response.getChannels().get("my_channel").size(), 2); + } + + @Test + public void testSyncAuthSuccess() throws PubNubException { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":[{\"message\":\"hihi\",\"timetoken\":\"14698320467224036\"}," + + "{\"message\":\"Hey\",\"timetoken\":\"14698320468265639\"}]," + + "\"mychannel\":[{\"message\":\"sample message\",\"timetoken\":\"14369823849575729\"}]}}"))); + + pubnub.getConfiguration().setAuthKey("authKey"); + + PNFetchMessagesResult response = + partialHistory.channels(Arrays.asList("mychannel,my_channel")).maximumPerChannel(25).sync(); + + assert response != null; + + Assert.assertEquals(response.getChannels().size(), 2); + Assert.assertTrue(response.getChannels().containsKey("mychannel")); + Assert.assertTrue(response.getChannels().containsKey("my_channel")); + Assert.assertEquals(response.getChannels().get("mychannel").size(), 1); + Assert.assertEquals(response.getChannels().get("my_channel").size(), 2); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals("authKey", requests.get(0).queryParameter("auth").firstValue()); + assertEquals(1, requests.size()); + } + + @Test + public void testSyncEncryptedSuccess() throws PubNubException { + pubnub.getConfiguration().setCipherKey("testCipher"); + + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/channel/mychannel,my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":[{\"message\":\"jC/yJ2y99BeYFYMQ7c53pg==\"," + + "\"timetoken\":\"14797423056306675\"}]," + + "\"mychannel\":[{\"message\":\"jC/yJ2y99BeYFYMQ7c53pg==\"," + + "\"timetoken\":\"14797423056306675\"}]}}"))); + + PNFetchMessagesResult response = + partialHistory.channels(Arrays.asList("mychannel,my_channel")).maximumPerChannel(25).sync(); + + assert response != null; + + Assert.assertEquals(response.getChannels().size(), 2); + Assert.assertTrue(response.getChannels().containsKey("mychannel")); + Assert.assertTrue(response.getChannels().containsKey("my_channel")); + Assert.assertEquals(response.getChannels().get("mychannel").size(), 1); + Assert.assertEquals(response.getChannels().get("my_channel").size(), 1); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/HeartbeatEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/HeartbeatEndpointTest.java new file mode 100644 index 000000000..56e607009 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/HeartbeatEndpointTest.java @@ -0,0 +1,166 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.presence.Heartbeat; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + +public class HeartbeatEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private Heartbeat partialHeartbeat; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + RetrofitManager retrofitManager = new RetrofitManager(pubnub); + partialHeartbeat = new Heartbeat(pubnub, null, retrofitManager, new TokenManager()); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSuccessOneChannel() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialHeartbeat.channels(Arrays.asList("ch1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + assertEquals("123", request.queryParameter("heartbeat").firstValue()); + + } + + @Test + public void testSuccessManyChannels() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialHeartbeat.channels(Arrays.asList("ch1", "ch2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + assertEquals("123", request.queryParameter("heartbeat").firstValue()); + } + + @Test + public void testSuccessOneChannelGroup() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialHeartbeat.channelGroups(Arrays.asList("cg1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + assertEquals("cg1", request.queryParameter("channel-group").firstValue()); + assertEquals("123", request.queryParameter("heartbeat").firstValue()); + } + + @Test + public void testSuccessManyChannelGroups() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialHeartbeat.channelGroups(Arrays.asList("cg1", "cg2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + assertEquals("cg1,cg2", request.queryParameter("channel-group").firstValue()); + assertEquals("123", request.queryParameter("heartbeat").firstValue()); + + } + + @Test(expected = PubNubException.class) + public void testMissingChannelAndGroupSync() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialHeartbeat.sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialHeartbeat.channels(Arrays.asList("ch1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + partialHeartbeat.channels(Arrays.asList("ch1")).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + partialHeartbeat.channels(Arrays.asList("ch1")).sync(); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/HistoryBatchEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/HistoryBatchEndpointTest.java new file mode 100644 index 000000000..1087a1d78 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/HistoryBatchEndpointTest.java @@ -0,0 +1,390 @@ +package com.pubnub.api.endpoints; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.FetchMessagesEnvelope; +import com.pubnub.api.services.HistoryService; +import okhttp3.Request; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Random; +import java.util.function.Supplier; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HistoryBatchEndpointTest { + private static final String TEST_CHANNEL_1 = "TEST_CHANNEL_1"; + private static final String TEST_CHANNEL_2 = "TEST_CHANNEL_2"; + private static final String SUBSCRIBE_KEY = "SUB_KEY"; + private static final String TEST_VERSION = "TEST_VERSION"; + private static final int MAX_FOR_FETCH_MESSAGES = 100; + private static final int MAX_FOR_FETCH_MESSAGES_WITH_ACTIONS = 25; + private static final int MULTIPLE_CHANNELS_MAX_FOR_FETCH_MESSAGES = 25; + private static final int EXPECTED_SINGLE_CHANNEL_DEFAULT_MESSAGES = 100; + private static final int EXPECTED_MULTIPLE_CHANNEL_DEFAULT_MESSAGES = 25; + private static final int EXPECTED_DEFAULT_MESSAGES_WITH_ACTIONS = 25; + private static final int EXPECTED_MAX_MESSAGES_WITH_ACTIONS = 25; + + private final PubNub pubnub = pubNubMock(); + private final HistoryService historyService = historyServiceMock(); + private final RetrofitManager retrofitManager = retrofitManagerMock(historyService); + private final TelemetryManager telemetryManager = mock(TelemetryManager.class); + private final ArgumentCaptor> optionsCaptor = ArgumentCaptor.forClass(Map.class); + + @Test + public void forSingleChannelFetchMessagesAlwaysPassMaxWhenItIsInBound() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = randomInt(MAX_FOR_FETCH_MESSAGES); + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(maximumPerChannel))))); + } + + @Test + public void forSingleChannelFetchMessagesAlwaysPassDefaultWhenNonPositiveMaxSpecified() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = -(randomInt(100) - 1); + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_SINGLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + + @Test + public void forSingleChannelFetchMessagesAlwaysPassDefaultWhenMaxNotSpecified() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_SINGLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + @Test + public void forSingleChannelFetchMessagesAlwaysPassDefaultMaxWhenMaxExceeds() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = MAX_FOR_FETCH_MESSAGES + randomInt(MAX_FOR_FETCH_MESSAGES); + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_SINGLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + @Test + public void forMultipleChannelsFetchMessagesAlwaysPassMaxWhenItIsInBound() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = randomInt(MULTIPLE_CHANNELS_MAX_FOR_FETCH_MESSAGES); + //when + fetchMessagesUnderTest + .channels(Arrays.asList(TEST_CHANNEL_1, TEST_CHANNEL_2)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(maximumPerChannel))))); + } + + @Test + public void forMultipleChannelsFetchMessagesAlwaysPassDefaultWhenNonPositiveMaxSpecified() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = -(randomInt(100) - 1); + //when + fetchMessagesUnderTest + .channels(Arrays.asList(TEST_CHANNEL_1, TEST_CHANNEL_2)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_MULTIPLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + + @Test + public void forMultipleChannelsFetchMessagesAlwaysPassDefaultWhenMaxNotSpecified() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + //when + fetchMessagesUnderTest + .channels(Arrays.asList(TEST_CHANNEL_1, TEST_CHANNEL_2)) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_MULTIPLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + @Test + public void forMultipleChannelsFetchMessagesAlwaysPassDefaultMaxWhenMaxExceeds() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = MULTIPLE_CHANNELS_MAX_FOR_FETCH_MESSAGES + randomInt(MULTIPLE_CHANNELS_MAX_FOR_FETCH_MESSAGES); + //when + fetchMessagesUnderTest + .channels(Arrays.asList(TEST_CHANNEL_1, TEST_CHANNEL_2)) + .maximumPerChannel(maximumPerChannel) + .sync(); + + //then + verify(historyService).fetchMessages(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_MULTIPLE_CHANNEL_DEFAULT_MESSAGES))))); + } + + @Test + public void forSingleChannelFetchMessagesWithActionAlwaysPassMaxWhenItIsInBound() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = randomInt(MAX_FOR_FETCH_MESSAGES_WITH_ACTIONS); + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .maximumPerChannel(maximumPerChannel) + .includeMessageActions(true) + .sync(); + + //then + verify(historyService).fetchMessagesWithActions(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(maximumPerChannel))))); + } + + @Test + public void forSingleChannelFetchMessagesWithActionAlwaysPassDefaultWhenMaxNotSpecified() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .includeMessageActions(true) + .sync(); + + //then + verify(historyService).fetchMessagesWithActions(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_DEFAULT_MESSAGES_WITH_ACTIONS))))); + } + + @Test + public void forSingleChannelFetchMessagesWithActionAlwaysPassDefaultMaxWhenMaxExceeds() throws PubNubException { + //given + final FetchMessages fetchMessagesUnderTest = new FetchMessages(pubnub, telemetryManager, retrofitManager, + new TokenManager()); + + final int maximumPerChannel = MAX_FOR_FETCH_MESSAGES_WITH_ACTIONS + + randomInt(MAX_FOR_FETCH_MESSAGES_WITH_ACTIONS); + //when + fetchMessagesUnderTest + .channels(Collections.singletonList(TEST_CHANNEL_1)) + .maximumPerChannel(maximumPerChannel) + .includeMessageActions(true) + .sync(); + + //then + verify(historyService).fetchMessagesWithActions(eq(SUBSCRIBE_KEY), any(), optionsCaptor.capture()); + + final Map capturedOptions = optionsCaptor.getValue(); + assertThat(capturedOptions, allOf( + notNullValue(), + hasEntry(is("max"), is(Integer.toString(EXPECTED_MAX_MESSAGES_WITH_ACTIONS))))); + } + + private static int randomInt(int max) { + final Random random = new Random(); + int randomInt = random.nextInt(max); + return randomInt + 1; + } + + @NotNull + private HistoryService historyServiceMock() { + final CallAdapter fetchMessagesEnvelopeCallAdapter = new CallAdapter<>(new Supplier>() { + @Override + public Response get() { + final FetchMessagesEnvelope fetchMessagesEnvelope = new FetchMessagesEnvelope(); + fetchMessagesEnvelope.setChannels(Collections.emptyMap()); + return Response.success(fetchMessagesEnvelope); + } + }); + final HistoryService historyService = mock(HistoryService.class); + when(historyService.fetchMessages(eq(SUBSCRIBE_KEY), any(), any())).thenAnswer(new Answer>() { + @Override + public Call answer(final InvocationOnMock invocation) { + return fetchMessagesEnvelopeCallAdapter; + } + }); + when(historyService.fetchMessagesWithActions(eq(SUBSCRIBE_KEY), any(), any())).thenAnswer(new Answer>() { + @Override + public Call answer(final InvocationOnMock invocation) { + return fetchMessagesEnvelopeCallAdapter; + } + }); + return historyService; + } + + private RetrofitManager retrofitManagerMock(final HistoryService historyService) { + final RetrofitManager retrofitManager = mock(RetrofitManager.class); + when(retrofitManager.getHistoryService()).thenReturn(historyService); + return retrofitManager; + } + + private PubNub pubNubMock() { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey(SUBSCRIBE_KEY); + + final PubNub pubnub = mock(PubNub.class); + when(pubnub.getConfiguration()).thenReturn(pnConfiguration); + when(pubnub.getVersion()).thenReturn(TEST_VERSION); + return pubnub; + + } + + private static class CallAdapter implements Call { + private final Supplier> responseSupplier; + + CallAdapter(final Supplier> responseSupplier) { + this.responseSupplier = responseSupplier; + } + + @Override + public Response execute() throws IOException { + return responseSupplier.get(); + } + + @Override + public void enqueue(final Callback callback) { + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public void cancel() { + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call clone() { + return null; + } + + @Override + public Request request() { + return null; + } + } +} + diff --git a/src/test/java/com/pubnub/api/endpoints/HistoryEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/HistoryEndpointTest.java new file mode 100644 index 000000000..3aa46218f --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/HistoryEndpointTest.java @@ -0,0 +1,476 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.history.PNHistoryResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +public class HistoryEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(PORT), false); + + private History partialHistory; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialHistory = pubnub.history(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncDisabled() { + String payload = "[[\"Use of the history API requires the Storage & Playback add-on which is not enabled for " + + "this subscribe key. Login to your PubNub Dashboard Account and ADD the Storage & Playback add-on. " + + "Contact support@pubnub.com if you require further assistance.\"],0,0]"; + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(payload))); + + try { + partialHistory.channel("niceChannel").sync(); + } catch (PubNubException ex) { + assertEquals("History is disabled", ex.getErrormsg()); + } + } + + @Test + public void testSyncWithTokensDisabled() { + String payload = "[\"Use of the history API requires the Storage & Playback which is not enabled for this " + + "subscribe key.Login to your PubNub Dashboard Account and enable Storage & Playback.Contact support " + + "@pubnub.com if you require further assistance.\",0,0]"; + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(payload))); + + try { + partialHistory.channel("niceChannel").includeTimetoken(true).sync(); + } catch (PubNubException ex) { + assertEquals("History is disabled", ex.getErrormsg()); + } + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + PNHistoryResult response = partialHistory.channel("niceChannel").includeTimetoken(true).sync(); + + assert response != null; + + assertEquals(1234L, (long) response.getStartTimetoken()); + assertEquals(4321L, (long) response.getEndTimetoken()); + + assertEquals(response.getMessages().size(), 2); + + assertEquals(1111L, (long) response.getMessages().get(0).getTimetoken()); + assertEquals((response.getMessages().get(0).getEntry()).getAsJsonObject().get("a").getAsInt(), 11); + assertEquals((response.getMessages().get(0).getEntry()).getAsJsonObject().get("b").getAsInt(), 22); + + assertEquals(2222L, (long) response.getMessages().get(1).getTimetoken()); + assertEquals((response.getMessages().get(1).getEntry()).getAsJsonObject().get("a").getAsInt(), 33); + assertEquals((response.getMessages().get(1).getEntry()).getAsJsonObject().get("b").getAsInt(), 44); + } + + @Test + public void testSyncAuthSuccess() throws PubNubException { + + pubnub.getConfiguration().setAuthKey("authKey"); + + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + partialHistory.channel("niceChannel").includeTimetoken(true).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals("authKey", requests.get(0).queryParameter("auth").firstValue()); + assertEquals(1, requests.size()); + } + + + @Test + public void testSyncEncryptedSuccess() throws IOException, PubNubException { + pubnub.getConfiguration().setCipherKey("testCipher"); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody("[[\"EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\\n\"," + + "\"EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\\n\"," + + "\"EGwV+Ti43wh2TprPIq7o0KMuW5j6B3yWy352ucWIOmU=\\n\"],14606134331557853,14606134485013970]"))); + + PNHistoryResult response = partialHistory.channel("niceChannel").includeTimetoken(false).sync(); + + assert response != null; + + assertEquals(14606134331557853L, (long) response.getStartTimetoken()); + assertEquals(14606134485013970L, (long) response.getEndTimetoken()); + + assertEquals(response.getMessages().size(), 3); + + assertNull(response.getMessages().get(0).getTimetoken()); + assertEquals("m1", (response.getMessages().get(0).getEntry()).getAsJsonArray().get(0).getAsString()); + assertEquals("m2", (response.getMessages().get(0).getEntry()).getAsJsonArray().get(1).getAsString()); + assertEquals("m3", (response.getMessages().get(0).getEntry()).getAsJsonArray().get(2).getAsString()); + + assertEquals("m1", (response.getMessages().get(1).getEntry()).getAsJsonArray().get(0).getAsString()); + assertEquals("m2", (response.getMessages().get(1).getEntry()).getAsJsonArray().get(1).getAsString()); + assertEquals("m3", (response.getMessages().get(1).getEntry()).getAsJsonArray().get(2).getAsString()); + + assertEquals("m1", (response.getMessages().get(2).getEntry()).getAsJsonArray().get(0).getAsString()); + assertEquals("m2", (response.getMessages().get(2).getEntry()).getAsJsonArray().get(1).getAsString()); + assertEquals("m3", (response.getMessages().get(2).getEntry()).getAsJsonArray().get(2).getAsString()); + + } + + @Test + public void testSyncEncryptedWithPNOtherSuccess() throws PubNubException { + pubnub.getConfiguration().setCipherKey("hello"); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody("[[{\"pn_other\":\"6QoqmS9CnB3W9+I4mhmL7w==\"}],14606134331557852," + + "14606134485013970]"))); + + PNHistoryResult response = partialHistory.channel("niceChannel").includeTimetoken(false).sync(); + + assert response != null; + + assertEquals(14606134331557852L, (long) response.getStartTimetoken()); + assertEquals(14606134485013970L, (long) response.getEndTimetoken()); + + assertEquals(response.getMessages().size(), 1); + + assertNull(response.getMessages().get(0).getTimetoken()); + assertEquals("hey", + response.getMessages().get(0).getEntry().getAsJsonObject().get("pn_other").getAsJsonObject().get( + "text").getAsString()); + + } + + @Test + public void testSyncSuccessWithoutTimeToken() throws PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + + historyItems.add(historyItem1); + historyItems.add(historyItem2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + PNHistoryResult response = partialHistory.channel("niceChannel").sync(); + + assert response != null; + + assertTrue(response.getStartTimetoken().equals(1234L)); + assertTrue(response.getEndTimetoken().equals(4321L)); + + assertEquals(response.getMessages().size(), 2); + + assertNull(response.getMessages().get(0).getTimetoken()); + assertEquals(response.getMessages().get(0).getEntry().getAsJsonObject().get("a").getAsInt(), 11); + assertEquals(response.getMessages().get(0).getEntry().getAsJsonObject().get("b").getAsInt(), 22); + + assertNull(response.getMessages().get(1).getTimetoken()); + assertEquals(response.getMessages().get(1).getEntry().getAsJsonObject().get("a").getAsInt(), 33); + assertEquals(response.getMessages().get(1).getEntry().getAsJsonObject().get("b").getAsInt(), 44); + } + + + @Test(expected = PubNubException.class) + public void testMissinChannel() throws IOException, PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + partialHistory.includeTimetoken(true).sync(); + } + + @Test(expected = PubNubException.class) + public void testChannelIsEmpty() throws PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + partialHistory.channel("").includeTimetoken(true).sync(); + } + + @Test + public void testOperationTypeSuccessAsync() throws PubNubException { + + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + final AtomicInteger atomic = new AtomicInteger(0); + partialHistory.channel("niceChannel").includeTimetoken(true).async(new PNCallback() { + @Override + public void onResponse(PNHistoryResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNHistoryOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testSyncCountReverseStartEndSuccess() throws IOException, PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + PNHistoryResult response = + partialHistory.channel("niceChannel").count(5).reverse(true).start(1L).end(2L).includeTimetoken(true).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/v2/history/sub-key/mySubscribeKey" + + "/channel/niceChannel.*"))); + assertTrue(requests.get(0).queryParameter("reverse").firstValue().equals("true")); + assertTrue(Integer.valueOf(requests.get(0).queryParameter("count").firstValue()).equals(5)); + assertTrue(Integer.valueOf(requests.get(0).queryParameter("start").firstValue()).equals(1)); + assertTrue(Integer.valueOf(requests.get(0).queryParameter("end").firstValue()).equals(2)); + assertTrue(requests.get(0).queryParameter("include_token").firstValue().equals("true")); + + + assertTrue(response.getStartTimetoken().equals(1234L)); + assertTrue(response.getEndTimetoken().equals(4321L)); + + assertEquals(response.getMessages().size(), 2); + + assertTrue(response.getMessages().get(0).getTimetoken().equals(1111L)); + assertEquals((response.getMessages().get(0).getEntry()).getAsJsonObject().get("a").getAsInt(), 11); + assertEquals((response.getMessages().get(0).getEntry()).getAsJsonObject().get("b").getAsInt(), 22); + + assertTrue(response.getMessages().get(1).getTimetoken().equals(2222L)); + assertEquals((response.getMessages().get(1).getEntry()).getAsJsonObject().get("a").getAsInt(), 33); + assertEquals((response.getMessages().get(1).getEntry()).getAsJsonObject().get("b").getAsInt(), 44); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSyncProcessMessageError() throws IOException, PubNubException { + List testArray = new ArrayList<>(); + List historyItems = new ArrayList<>(); + + Map historyEnvelope1 = new HashMap<>(); + Map historyItem1 = new HashMap<>(); + historyItem1.put("a", 11); + historyItem1.put("b", 22); + historyEnvelope1.put("timetoken", 1111); + historyEnvelope1.put("message", historyItem1); + + Map historyEnvelope2 = new HashMap<>(); + Map historyItem2 = new HashMap<>(); + historyItem2.put("a", 33); + historyItem2.put("b", 44); + historyEnvelope2.put("timetoken", 2222); + historyEnvelope2.put("message", historyItem2); + + historyItems.add(historyEnvelope1); + historyItems.add(historyEnvelope2); + + testArray.add(historyItems); + testArray.add(1234); + testArray.add(4321); + + stubFor(get(urlPathEqualTo("/v2/history/sub-key/mySubscribeKey/channel/niceChannel")) + .willReturn(aResponse().withBody(pubnub.getMapper().toJson(testArray)))); + + pubnub.getConfiguration().setCipherKey("Test"); + partialHistory.channel("niceChannel").count(5).reverse(true).start(1L).end(2L).includeTimetoken(true).sync(); + } + + +} diff --git a/src/test/java/com/pubnub/api/endpoints/MessageCountTest.java b/src/test/java/com/pubnub/api/endpoints/MessageCountTest.java new file mode 100644 index 000000000..480cc5de4 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/MessageCountTest.java @@ -0,0 +1,270 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.models.consumer.history.PNMessageCountResult; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + + +public class MessageCountTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncDisabled() { + + String payload = "[\"Use of the history API requires the Storage & Playback which is not enabled for this " + + "subscribe key.Login to your PubNub Dashboard Account and enable Storage & Playback.Contact support " + + "@pubnub.com if you require further assistance.\",0,0]"; + + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody(payload))); + + try { + pubnub.messageCounts() + .channels(Collections.singletonList("my_channel")) + .channelsTimetoken(Collections.singletonList(10000L)) + .sync(); + } catch (PubNubException ex) { + assertEquals("History is disabled", ex.getErrormsg()); + } + } + + @Test + public void testSingleChannelWithSingleToken() throws PubNubException { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}"))); + + PNMessageCountResult response = pubnub.messageCounts() + .channels(Collections.singletonList("my_channel")) + .channelsTimetoken(Collections.singletonList(10000L)) + .sync(); + + assert response != null; + + assertEquals(response.getChannels().size(), 1); + assertFalse(response.getChannels().containsKey("channel_does_not_exist")); + assertTrue(response.getChannels().containsKey("my_channel")); + for (Map.Entry stringLongEntry : response.getChannels().entrySet()) { + assertEquals("my_channel", stringLongEntry.getKey()); + assertEquals(Long.valueOf("19"), stringLongEntry.getValue()); + } + } + + @Test + public void testSingleChannelWithMultipleTokens() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}"))); + + PubNubException exception = null; + + try { + pubnub.messageCounts() + .channels(Collections.singletonList("my_channel")) + .channelsTimetoken(Arrays.asList(10000L, 20000L)) + .sync(); + } catch (PubNubException e) { + exception = e; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_CHANNELS_TIMETOKEN_MISMATCH.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + @Test + public void testMultipleChannelsWithSingleToken() throws PubNubException { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}"))); + + PNMessageCountResult response = pubnub.messageCounts() + .channels(Arrays.asList("my_channel", "new_channel")) + .channelsTimetoken(Collections.singletonList(10000L)) + .sync(); + + assert response != null; + + assertEquals(response.getChannels().size(), 2); + assertFalse(response.getChannels().containsKey("channel_does_not_exist")); + assertTrue(response.getChannels().containsKey("my_channel")); + assertTrue(response.getChannels().containsKey("new_channel")); + + for (Map.Entry stringLongEntry : response.getChannels().entrySet()) { + if (stringLongEntry.getKey().equals("my_channel")) { + assertEquals(Long.valueOf("19"), stringLongEntry.getValue()); + } else if (stringLongEntry.getKey().equals("new_channel")) { + assertEquals(Long.valueOf("5"), stringLongEntry.getValue()); + } + } + } + + @Test + public void testMultipleChannelsWithMultipleTokens() throws PubNubException { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel,new_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}"))); + + PNMessageCountResult response = pubnub.messageCounts() + .channels(Arrays.asList("my_channel", "new_channel")) + .channelsTimetoken(Arrays.asList(10000L, 20000L)) + .sync(); + + assert response != null; + + assertEquals(response.getChannels().size(), 2); + assertFalse(response.getChannels().containsKey("channel_does_not_exist")); + assertTrue(response.getChannels().containsKey("my_channel")); + assertTrue(response.getChannels().containsKey("new_channel")); + + for (Map.Entry stringLongEntry : response.getChannels().entrySet()) { + if (stringLongEntry.getKey().equals("my_channel")) { + assertEquals(Long.valueOf("19"), stringLongEntry.getValue()); + } else if (stringLongEntry.getKey().equals("new_channel")) { + assertEquals(Long.valueOf("5"), stringLongEntry.getValue()); + } + } + } + + @Test + public void testWithoutTimeToken() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}"))); + + PubNubException exception = null; + try { + pubnub.messageCounts() + .channels(Collections.singletonList("my_channel")) + .sync(); + } catch (PubNubException ex) { + exception = ex; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_TIMETOKEN_MISSING.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + @Test + public void testWithoutChannelsSingleToken() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}"))); + + PubNubException exception = null; + try { + pubnub.messageCounts() + .channelsTimetoken(Collections.singletonList(10000L)) + .sync(); + } catch (PubNubException ex) { + exception = ex; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + @Test + public void testWithoutChannelsMultipleTokens() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19, \"new_channel\":5}}"))); + + PubNubException exception = null; + try { + pubnub.messageCounts() + .channelsTimetoken(Arrays.asList(10000L, 20000L)) + .sync(); + } catch (PubNubException ex) { + exception = ex; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + @Test + public void testChannelWithSingleEmptyToken() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}"))); + + PubNubException exception = null; + try { + pubnub.messageCounts() + .channels(Collections.singletonList("my_channel")) + .channelsTimetoken(Collections.singletonList(null)) + .sync(); + } catch (PubNubException ex) { + exception = ex; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_TIMETOKEN_MISSING.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + @Test + public void testChannelWithMultipleNullTokens() { + stubFor(get(urlPathEqualTo("/v3/history/sub-key/mySubscribeKey/message-counts/my_channel")) + .willReturn(aResponse().withBody("{\"status\": 200, \"error\": false, \"error_message\": \"\", " + + "\"channels\": {\"my_channel\":19}}"))); + + PubNubException exception = null; + try { + pubnub.messageCounts() + .channels(Arrays.asList("my_channel", "my_channel_1", "my_channel_2")) + .channelsTimetoken(Arrays.asList(10000L, null, 20000L)) + .sync(); + } catch (PubNubException ex) { + exception = ex; + } finally { + assertNotNull(exception); + assertEquals(PubNubErrorBuilder.PNERROBJ_TIMETOKEN_MISSING.getMessage(), + exception.getPubnubError().getMessage()); + } + } + + +} diff --git a/src/test/java/com/pubnub/api/endpoints/TestHarness.java b/src/test/java/com/pubnub/api/endpoints/TestHarness.java new file mode 100644 index 000000000..b6fb21982 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/TestHarness.java @@ -0,0 +1,50 @@ +package com.pubnub.api.endpoints; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.enums.PNLogVerbosity; + +public class TestHarness { + protected final static int PORT = 8080; + + protected PubNub createPubNubInstance() { + PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setOrigin("localhost" + ":" + PORT); + pnConfiguration.setSecure(false); + pnConfiguration.setSubscribeKey("mySubscribeKey"); + pnConfiguration.setPublishKey("myPublishKey"); + pnConfiguration.setUuid("myUUID"); + pnConfiguration.setLogVerbosity(PNLogVerbosity.BODY); + pnConfiguration.setUseRandomInitializationVector(false); + + class MockedTimePubNub extends PubNub { + + public MockedTimePubNub(PNConfiguration initialConfig) { + super(initialConfig); + } + + @Override + public int getTimestamp() { + return 1337; + } + + @Override + public String getVersion() { + return "suchJava"; + } + + @Override + public String getInstanceId() { + return "PubNubInstanceId"; + } + + @Override + public String getRequestId() { + return "PubNubRequestId"; + } + + } + + return new MockedTimePubNub(pnConfiguration); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/TimeEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/TimeEndpointTest.java new file mode 100644 index 000000000..70895d1cf --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/TimeEndpointTest.java @@ -0,0 +1,216 @@ +package com.pubnub.api.endpoints; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.TimeCallback; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.PNTimeResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + +public class TimeEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private Time partialTime; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialTime = pubnub.time(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]"))); + + PNTimeResult response = partialTime.sync(); + + assert response != null; + + assertEquals(14593046077243110L, (long) response.getTimetoken()); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithString() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[abc]"))); + partialTime.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithoutJSON() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("zimp"))); + partialTime.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithout200() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]").withStatus(404))); + PNTimeResult response = partialTime.sync(); + + assert response != null; + + assertEquals(14593046077243110L, (long) response.getTimetoken()); + } + + @Test + public void testAsyncSuccess() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]"))); + final AtomicInteger atomic = new AtomicInteger(0); + partialTime.async(new TimeCallback() { + + @Override + public void onResponse(@Nullable PNTimeResult result, @NotNull PNStatus status) { + assert result != null; + assertEquals(14593046077243110L, (long) result.getTimetoken()); + atomic.incrementAndGet(); + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testAsyncRetrySuccess() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]"))); + final AtomicInteger atomic = new AtomicInteger(0); + partialTime.async(new TimeCallback() { + + @Override + public void onResponse(@Nullable PNTimeResult result, @NotNull PNStatus status) { + assert result != null; + assertEquals(14593046077243110L, (long) result.getTimetoken()); + atomic.incrementAndGet(); + + if (atomic.get() == 1) { + status.retry(); + } + + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(2)); + } + + @Test + public void testAsyncBrokenWithString() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[abc]"))); + final AtomicInteger atomic = new AtomicInteger(0); + partialTime.async(new TimeCallback() { + + @Override + public void onResponse(PNTimeResult result, @NotNull PNStatus status) { + if (status != null) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testAsyncBrokenWithoutJSON() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("zimp"))); + final AtomicInteger atomic = new AtomicInteger(0); + partialTime.async(new TimeCallback() { + + @Override + public void onResponse(PNTimeResult result, @NotNull PNStatus status) { + if (status != null) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testAsyncBrokenWithout200() throws IOException, PubNubException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]").withStatus(404))); + final AtomicInteger atomic = new AtomicInteger(0); + partialTime.async(new TimeCallback() { + + @Override + public void onResponse(PNTimeResult result, @NotNull PNStatus status) { + if (status != null) { + atomic.incrementAndGet(); + } + } + + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/time/0")) + .willReturn(aResponse().withBody("[14593046077243110]"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + PNTimeResult response = partialTime.sync(); + + assert response != null; + + assertEquals(14593046077243110L, (long) response.getTimetoken()); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/access/GrantEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/access/GrantEndpointTest.java new file mode 100644 index 000000000..d3e06cfeb --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/access/GrantEndpointTest.java @@ -0,0 +1,1128 @@ +package com.pubnub.api.endpoints.access; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerGrantResult; +import com.pubnub.api.models.consumer.access_manager.PNAccessManagerKeyData; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + +public class GrantEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private Grant partialGrant; + private PubNub pubnub; + private String uuid = "myUUID"; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialGrant = pubnub.grant(); + pubnub.getConfiguration().setSecretKey("secretKey").setIncludeInstanceIdentifier(true); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void noGroupsOneChannelOneKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Collections.singletonList("key1")).channels( + Collections.singletonList("ch1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + + } + + @Test + public void noGroupsOneChannelTwoKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0,\"w\":0," + + "\"m\":0}}},\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channels(Collections.singletonList("ch1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsTwoChannelOneKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}}," + + "\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\"," + + "\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Collections.singletonList("key1")).channels(Arrays.asList("ch1", "ch2")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(1, result.getChannels().get("ch2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsTwoChannelTwoKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0," + + "\"w\":0,\"m\":0}}},\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0," + + "\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channels(Arrays.asList("ch1", "ch2")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(2, result.getChannels().get("ch2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void oneGroupNoChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel-groups\":\"cg1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}}," + + "\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = partialGrant.authKeys(Arrays.asList("key1")).channelGroups(Arrays.asList( + "cg1")).sync(); + + assert result != null; + + assertEquals(0, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void oneGroupNoChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel-groups\":\"cg1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0," + + "\"w\":0,\"m\":0}}},\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channelGroups(Arrays.asList("cg1")).sync(); + + assert result != null; + + assertEquals(0, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void oneGroupOneChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}," + + "\"channel-groups\":\"cg1\"},\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).channelGroups( + Arrays.asList("cg1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void oneGroupOneChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0,\"w\":0," + + "\"m\":0}},\"channel-groups\":\"cg1\"},\"service\":\"Access Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channels(Arrays.asList("ch1")).channelGroups( + Arrays.asList("cg1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + + } + + @Test + public void oneGroupTwoChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}}," + + "\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}}},\"channel-groups\":\"cg1\"," + + "\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1" + , "ch2")).channelGroups(Arrays.asList("cg1")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + + } + + @Test + public void oneGroupTwoChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0," + + "\"w\":0,\"m\":0}}},\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0," + + "\"w\":0,\"m\":0}}}},\"channel-groups\":\"cg1\",\"auths\":{\"key1\":{\"r\":0,\"w\":0," + + "\"m\":0},\"key2\":{\"r\":0,\"w\":0,\"m\":0}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channels( + Arrays.asList("ch1", "ch2")).channelGroups(Arrays.asList("cg1")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(1, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + + @Test + public void twoGroupNoChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}," + + "\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = partialGrant.authKeys(Arrays.asList("key1")).channelGroups(Arrays.asList( + "cg1", "cg2")).sync(); + + assert result != null; + + assertEquals(0, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(1, result.getChannelGroups().get("cg2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void twoGroupNoChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}," + + "\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}," + + "\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\",\"status\":200}\n"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channelGroups(Arrays.asList("cg1", "cg2")).sync(); + + assert result != null; + + assertEquals(0, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void twoGroupOneChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}," + + "\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}," + + "\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = partialGrant.authKeys(Arrays.asList("key1")).channelGroups(Arrays.asList( + "cg1", "cg2")).channels(Arrays.asList("ch1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(1, result.getChannelGroups().get("cg2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void twoGroupOneChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0," + + "\"m\":0}},\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}," + + "\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}," + + "\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\",\"status\":200}\n"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channelGroups( + Arrays.asList("cg1", "cg2")).channels(Arrays.asList("ch1")).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(2, result.getChannelGroups().get("cg2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key2").getClass()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void twoGroupTwoChannelOneKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}," + + "\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}," + + "\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}," + + "\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = partialGrant.authKeys(Arrays.asList("key1")).channelGroups(Arrays.asList( + "cg1", "cg2")).channels(Arrays.asList("ch1", "ch2")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(1, result.getChannelGroups().get("cg1").size()); + assertEquals(1, result.getChannelGroups().get("cg2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(1, result.getChannels().get("ch2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void twoGroupTwoChannelTwoKey() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1,ch2")) + .withQueryParam("channel-group", matching("cg1,cg2")) + .withQueryParam("auth", matching("key1,key2")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"channel-group+auth\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channels\":{\"ch1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0," + + "\"w\":0,\"m\":0}}},\"ch2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0},\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0," + + "\"w\":0,\"m\":0}}}},\"channel-groups\":{\"cg1\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0," + + "\"m\":0},\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"cg2\":{\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0," + + "\"m\":0},\"key2\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}}}},\"service\":\"Access Manager\"," + + "\"status\":200}\n"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1", "key2")).channelGroups( + Arrays.asList("cg1", "cg2")).channels(Arrays.asList("ch1", "ch2")).sync(); + + assert result != null; + + assertEquals(2, result.getChannels().size()); + assertEquals(2, result.getChannelGroups().size()); + + assertEquals(2, result.getChannelGroups().get("cg1").size()); + assertEquals(2, result.getChannelGroups().get("cg2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannelGroups().get("cg2").get("key2").getClass()); + + assertEquals(2, result.getChannels().get("ch1").size()); + assertEquals(2, result.getChannels().get("ch2").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key2").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key1").getClass()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch2").get("key2").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsOneChannelOneKeyTTLTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .withQueryParam("ttl", matching("1334")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).ttl(1334).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsOneChannelOneReadKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("1")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).read(true).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsOneChannelOneWriteKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("1")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).write(true).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsOneChannelOneDeleteKeyTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .withQueryParam("d", matching("1")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).delete(true).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void noGroupsOneChannelOneKeyManageTest() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("1")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + PNAccessManagerGrantResult result = + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).manage(true).sync(); + + assert result != null; + + assertEquals(1, result.getChannels().size()); + assertEquals(0, result.getChannelGroups().size()); + + assertEquals(1, result.getChannels().get("ch1").size()); + assertEquals(PNAccessManagerKeyData.class, result.getChannels().get("ch1").get("key1").getClass()); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("instanceid", matching("PubNubInstanceId")) + .withQueryParam("requestid", matching("PubNubRequestId")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialGrant.authKeys(Collections.singletonList("key1")).channels(Collections.singletonList("ch1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/v2/auth/grant/sub-key/mySubscribeKey.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + AtomicBoolean atomic = new AtomicBoolean(false); + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + partialGrant.authKeys(Collections.singletonList("key1")).channels(Collections.singletonList("ch1")).async( + new PNCallback() { + @Override + public void onResponse(PNAccessManagerGrantResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNAccessManagerGrant) { + atomic.set(true); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic); + } + + @Test + public void testNullSecretKey() { + pubnub.getConfiguration().setSecretKey(null); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Secret Key not configured.", e.getPubnubError().getMessage()); + } + } + + @Test + public void testEmptySecretKey() { + pubnub.getConfiguration().setSecretKey(""); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Secret Key not configured.", e.getPubnubError().getMessage()); + } + } + + @Test + public void testNullSubscribeKey() { + pubnub.getConfiguration().setSubscribeKey(null); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Subscribe Key not configured.", e.getPubnubError().getMessage()); + } + } + + @Test + public void testEmptySubscribeKey() { + pubnub.getConfiguration().setSubscribeKey(""); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Subscribe Key not configured.", e.getPubnubError().getMessage()); + } + } + + @Test + public void testNullPublishKey() { + pubnub.getConfiguration().setPublishKey(null); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Publish Key not configured.", e.getPubnubError().getMessage()); + } + + } + + @Test + public void testEmptyPublishKey() { + pubnub.getConfiguration().setPublishKey(""); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + throw new RuntimeException("should never reach here"); + } catch (PubNubException e) { + assertEquals("ULS configuration failed. Publish Key not configured.", e.getPubnubError().getMessage()); + } + } + + @Test + public void testMissingChannelsAndChannelGroup() { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withStatus(200).withBody("{\"message\":\"Success\"," + + "\"payload\":{\"level\":\"subkey\",\"subscribe_key\":\"mySubscribeKey\",\"ttl\":1440,\"r\":0," + + "\"w\":1,\"m\":0,\"d\":0},\"service\":\"Access Manager\",\"status\":200}"))); + + try { + PNAccessManagerGrantResult grantResult = partialGrant.sync(); + Assert.assertNotNull(grantResult); + assertEquals("subkey", grantResult.getLevel()); + assertEquals(1440, grantResult.getTtl()); + assertEquals(0, grantResult.getChannels().size()); + assertEquals(0, grantResult.getChannelGroups().size()); + } catch (PubNubException e) { + e.printStackTrace(); + throw new RuntimeException("should never reach here"); + } + } + + @Test + public void testNullPayload() { + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("auth", matching("key1")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"service\":\"Access Manager\"," + + "\"status\":200}"))); + + try { + partialGrant.authKeys(Arrays.asList("key1")).channels(Arrays.asList("ch1")).sync(); + } catch (PubNubException e) { + assertEquals("Parsing Error", e.getPubnubError().getMessage()); + } + } + + @Test + public void testNullAuthKeyAsync() throws PubNubException { + + AtomicBoolean atomic = new AtomicBoolean(false); + + stubFor(get(urlPathEqualTo("/v2/auth/grant/sub-key/mySubscribeKey")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("channel", matching("ch1")) + .withQueryParam("uuid", matching(uuid)) + .withQueryParam("timestamp", matching("1337")) + .withQueryParam("r", matching("0")) + .withQueryParam("w", matching("0")) + .withQueryParam("m", matching("0")) + .willReturn(aResponse().withBody("{\"message\":\"Success\",\"payload\":{\"level\":\"user\"," + + "\"subscribe_key\":\"sub-c-82ab2196-b64f-11e5-8622-0619f8945a4f\",\"ttl\":1," + + "\"channel\":\"ch1\",\"auths\":{\"key1\":{\"r\":0,\"d\":0,\"g\":0,\"u\":0,\"j\":0,\"w\":0,\"m\":0}}},\"service\":\"Access " + + "Manager\",\"status\":200}"))); + + partialGrant.channels(Collections.singletonList("ch1")).async(new PNCallback() { + @Override + public void onResponse(PNAccessManagerGrantResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNAccessManagerGrant + && !status.isError()) { + atomic.set(true); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic); + } + + @Test(expected = PubNubException.class) + public void grantsForUUIDRequireAuthKey() throws PubNubException { + pubnub.grant() + .uuids(Collections.singletonList(uuid)) + .update(true) + .sync(); + } + + @Test(expected = PubNubException.class) + public void grantsForUUIDCannotBeMadeWithChannels() throws PubNubException { + pubnub.grant() + .uuids(Collections.singletonList(uuid)) + .authKeys(Collections.singletonList("authKey")) + .update(true) + .channels(Collections.singletonList("channel")) + .sync(); + } + + @Test(expected = PubNubException.class) + public void grantsForUUIDCannotBeMadeWithChannelGroups() throws PubNubException { + pubnub.grant() + .uuids(Collections.singletonList(uuid)) + .authKeys(Collections.singletonList("authKey")) + .update(true) + .channelGroups(Collections.singletonList("channelGroup")) + .sync(); + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java new file mode 100644 index 000000000..eefd348f9 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java @@ -0,0 +1,75 @@ +package com.pubnub.api.endpoints.access; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_RESOURCES_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_SECRET_KEY_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_SUBSCRIBE_KEY_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_TTL_MISSING; +import static org.junit.Assert.assertEquals; + +public class GrantTokenEndpointTest extends TestHarness { + + private final PubNub pubnub = this.createPubNubInstance(); + + @Before + public void beforeEach() throws IOException { + pubnub.getConfiguration().setSecretKey("secretKey").setIncludeInstanceIdentifier(true); + } + + @Test + public void validate_NoResourceSet() { + try { + pubnub.grantToken() + .ttl(1) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_RESOURCES_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_NoTTLSet() { + try { + pubnub.grantToken() + .channels(Collections.singletonList(ChannelGrant.name("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_TTL_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_SecretKeyMissing() { + try { + createPubNubInstance().grantToken() + .ttl(1) + .channelGroups(Collections.singletonList(ChannelGroupGrant.id("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_SECRET_KEY_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_SubscribeKeyMissing() { + try { + new PubNub(new PNConfiguration().setSecretKey("secret")).grantToken() + .ttl(1) + .channelGroups(Collections.singletonList(ChannelGroupGrant.id("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_SUBSCRIBE_KEY_MISSING, e.getPubnubError().getErrorCode()); + } + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.java new file mode 100644 index 000000000..4e7c3fbfa --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/channel_groups/AddChannelChannelGroupEndpointTest.java @@ -0,0 +1,145 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAddChannelResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AddChannelChannelGroupEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private AddChannelChannelGroup partialAddChannelChannelGroup; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialAddChannelChannelGroup = pubnub.addChannelsToChannelGroup(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + PNChannelGroupsAddChannelResult response = + partialAddChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).sync(); + + assertNotNull(response); + } + + @Test(expected = PubNubException.class) + public void testSyncGroupMissing() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + partialAddChannelChannelGroup.channels(Arrays.asList("ch1", "ch2")).sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncGroupIsEmpty() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + partialAddChannelChannelGroup.channelGroup("").channels(Arrays.asList("ch1", "ch2")).sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncChannelMissing() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + partialAddChannelChannelGroup.channelGroup("groupA").sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialAddChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {} , " + + "\"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialAddChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsAddChannelResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNAddChannelsToGroupOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testErrorBodyForbiden() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withStatus(403).withBody("{\"status\": 403, \"message\": \"OK\", \"payload\":" + + " {} , \"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialAddChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsAddChannelResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNAddChannelsToGroupOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.java new file mode 100644 index 000000000..47749d0ba --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/channel_groups/AllChannelsChannelGroupEndpointTest.java @@ -0,0 +1,136 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsAllChannelsResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class AllChannelsChannelGroupEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private AllChannelsChannelGroup partialAllChannelsChannelGroup; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialAllChannelsChannelGroup = pubnub.listChannelsForChannelGroup(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + PNChannelGroupsAllChannelsResult response = partialAllChannelsChannelGroup.channelGroup("groupA").sync(); + + assert response != null; + + assertThat(response.getChannels(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test(expected = PubNubException.class) + public void testSyncMissingGroup() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + partialAllChannelsChannelGroup.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncEmptyGroup() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + partialAllChannelsChannelGroup.channelGroup("").sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPayload() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": " + + "\"ChannelGroups\"}"))); + + PNChannelGroupsAllChannelsResult response = partialAllChannelsChannelGroup.channelGroup("groupA").sync(); + + assert response != null; + + assertThat(response.getChannels(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialAllChannelsChannelGroup.channelGroup("groupA").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialAllChannelsChannelGroup.channelGroup("groupA").async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsAllChannelsResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNChannelsForGroupOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + +} diff --git a/src/test/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.java new file mode 100644 index 000000000..07a57b869 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/channel_groups/DeleteChannelGroupEndpointTest.java @@ -0,0 +1,113 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsDeleteGroupResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class DeleteChannelGroupEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private DeleteChannelGroup partialDeleteChannelGroup; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialDeleteChannelGroup = pubnub.deleteChannelGroup(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + PNChannelGroupsDeleteGroupResult response = partialDeleteChannelGroup.channelGroup("groupA").sync(); + assertNotNull(response); + } + + @Test(expected = PubNubException.class) + public void testSyncMissingGroup() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + partialDeleteChannelGroup.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncEmptyGroup() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + partialDeleteChannelGroup.channelGroup("").sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialDeleteChannelGroup.channelGroup("groupA").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA/remove")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialDeleteChannelGroup.channelGroup("groupA").async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsDeleteGroupResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNRemoveGroupOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.java new file mode 100644 index 000000000..73d558258 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/channel_groups/ListAllChannelGroupEndpointTest.java @@ -0,0 +1,123 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsListAllResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class ListAllChannelGroupEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private ListAllChannelGroup partialChannelGroup; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialChannelGroup = pubnub.listAllChannelGroups(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + PNChannelGroupsListAllResult response = partialChannelGroup.sync(); + + assert response != null; + + assertThat(response.getGroups(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test(expected = PubNubException.class) + public void testNullPayload() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": " + + "\"ChannelGroups\"}"))); + + PNChannelGroupsListAllResult response = partialChannelGroup.sync(); + + assert response != null; + + assertThat(response.getGroups(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test(expected = PubNubException.class) + public void testNullBody() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse())); + + PNChannelGroupsListAllResult response = partialChannelGroup.sync(); + + assert response != null; + + assertThat(response.getGroups(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialChannelGroup.sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"groups\": " + + "[\"a\",\"b\"]}, \"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialChannelGroup.async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsListAllResult result, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNChannelGroupsOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.java new file mode 100644 index 000000000..d03c484dc --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/channel_groups/RemoveChannelChannelGroupEndpointTest.java @@ -0,0 +1,114 @@ +package com.pubnub.api.endpoints.channel_groups; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.channel_group.PNChannelGroupsRemoveChannelResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class RemoveChannelChannelGroupEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private RemoveChannelChannelGroup partialRemoveChannelChannelGroup; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialRemoveChannelChannelGroup = pubnub.removeChannelsFromChannelGroup(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + PNChannelGroupsRemoveChannelResult response = + partialRemoveChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).sync(); + assertNotNull(response); + } + + @Test(expected = PubNubException.class) + public void testSyncMissinGroup() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + partialRemoveChannelChannelGroup.channels(Arrays.asList("ch1", "ch2")).sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncMissinChannel() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + partialRemoveChannelChannelGroup.channelGroup("groupA").sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialRemoveChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/channel-registration/sub-key/mySubscribeKey/channel-group/groupA")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {}, " + + "\"service\": \"ChannelGroups\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialRemoveChannelChannelGroup.channelGroup("groupA").channels(Arrays.asList("ch1", "ch2")).async(new PNCallback() { + @Override + public void onResponse(PNChannelGroupsRemoveChannelResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNRemoveChannelsFromGroupOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/files/GetFileUrlTest.java b/src/test/java/com/pubnub/api/endpoints/files/GetFileUrlTest.java new file mode 100644 index 000000000..1e2d8dcb7 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/files/GetFileUrlTest.java @@ -0,0 +1,118 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.files.PNFileUrlResult; +import okhttp3.HttpUrl; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class GetFileUrlTest { + + private final String channel = "channel"; + private final String fileName = "fileName"; + private final String fileId = "fileId"; + private final Set defaultQueryParams = new HashSet<>(Arrays.asList("pnsdk", "requestid", "uuid")); + + @Test + public void noAdditionalQueryParamsWhenNotSecretNorAuth() throws PubNubException { + //given + PubNub pubnub = new PubNub(config()); + + //when + PNFileUrlResult result = pubnub.getFileUrl() + .channel(channel) + .fileName(fileName) + .fileId(fileId) + .sync(); + + //then + Collection queryParamNames = queryParameterNames(result.getUrl()); + queryParamNames.removeAll(defaultQueryParams); + Assert.assertEquals(Collections.emptySet(), queryParamNames); + } + + @Test + public void signatureAndTimestampQueryParamsAreSetWhenSecret() throws PubNubException { + //given + PubNub pubnub = new PubNub(withSecret(config())); + + //when + PNFileUrlResult result = pubnub.getFileUrl() + .channel(channel) + .fileName(fileName) + .fileId(fileId) + .sync(); + + //then + Collection queryParamNames = queryParameterNames(result.getUrl()); + queryParamNames.removeAll(defaultQueryParams); + Assert.assertThat(queryParamNames, Matchers.containsInAnyOrder("signature", "timestamp")); + } + + @Test + public void authQueryParamIsSetWhenAuth() throws PubNubException { + //given + PubNub pubnub = new PubNub(withAuth(config())); + + //when + PNFileUrlResult result = pubnub.getFileUrl() + .channel(channel) + .fileName(fileName) + .fileId(fileId) + .sync(); + + //then + Collection queryParamNames = queryParameterNames(result.getUrl()); + queryParamNames.removeAll(defaultQueryParams); + Assert.assertThat(queryParamNames, Matchers.containsInAnyOrder("auth")); + } + + @Test + public void signatureAndTimestampAndAuthQueryParamsAreSetWhenSecretAndAuth() throws PubNubException { + //given + PubNub pubnub = new PubNub(withSecret(withAuth(config()))); + + //when + PNFileUrlResult result = pubnub.getFileUrl() + .channel(channel) + .fileName(fileName) + .fileId(fileId) + .sync(); + + //then + System.out.println(result.getUrl()); + Collection queryParamNames = queryParameterNames(result.getUrl()); + queryParamNames.removeAll(defaultQueryParams); + Assert.assertThat(queryParamNames, Matchers.containsInAnyOrder("auth", "signature", "timestamp")); + } + + private PNConfiguration config() { + PNConfiguration config = new PNConfiguration(); + config.setPublishKey("pk"); + config.setSubscribeKey("sk"); + return config; + } + + private PNConfiguration withSecret(PNConfiguration config) { + config.setSecretKey("secK"); + return config; + } + + private PNConfiguration withAuth(PNConfiguration config) { + config.setAuthKey("ak"); + return config; + } + + private Collection queryParameterNames(String url) { + return new HashSet<>(HttpUrl.get(url).queryParameterNames()); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/files/SendFileTest.java b/src/test/java/com/pubnub/api/endpoints/files/SendFileTest.java new file mode 100644 index 000000000..ff187137c --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/files/SendFileTest.java @@ -0,0 +1,268 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.remoteaction.TestRemoteAction; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.files.PNBaseFile; +import com.pubnub.api.models.consumer.files.PNFileUploadResult; +import com.pubnub.api.models.consumer.files.PNPublishFileMessageResult; +import com.pubnub.api.models.server.files.FileUploadRequestDetails; +import com.pubnub.api.models.server.files.FormField; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.PubNubUtil.readBytes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SendFileTest implements TestsWithFiles { + private final String channel = "channel"; + private final String filename = "test.txt"; + private final GenerateUploadUrl.Factory generateUploadUrlFactory = mock(GenerateUploadUrl.Factory.class); + private final PublishFileMessage.Builder publishFileMessageBuilder = mock(PublishFileMessage.Builder.class, + RETURNS_DEEP_STUBS); + private final UploadFile.Factory sendFileToS3Factory = mock(UploadFile.Factory.class); + + @Override + @Rule + public TemporaryFolder getTemporaryFolder() { + return folder; + } + + @Test + public void sync_happyPath() throws PubNubException, IOException { + //given + File file = getTemporaryFile(filename); + FileUploadRequestDetails fileUploadRequestDetails = generateUploadUrlProperResponse(); + PNFileUploadResult expectedResponse = pnFileUploadResult(); + PNPublishFileMessageResult publishFileMessageResult = new PNPublishFileMessageResult(expectedResponse.getTimetoken()); + + when(generateUploadUrlFactory.create(any(), any())).thenReturn(TestRemoteAction.successful( + fileUploadRequestDetails)); + when(sendFileToS3Factory.create(any(), any(), any(), any())).thenReturn(TestRemoteAction.successful(null)); + PublishFileMessage publishFileMessage = AlwaysSuccessfulPublishFileMessage.create(publishFileMessageResult); + when(publishFileMessageBuilder.channel(any()).fileName(any()).fileId(any())) + .thenReturn(publishFileMessage); + + //when + PNFileUploadResult result; + try (FileInputStream fileInputStream = new FileInputStream(file)) { + result = sendFile(channel, file.getName(), fileInputStream).sync(); + } + + //then + assertEquals(expectedResponse, result); + } + + @Test + public void async_happyPath() throws InterruptedException, IOException { + //given + CountDownLatch countDownLatch = new CountDownLatch(1); + File file = getTemporaryFile(filename); + FileUploadRequestDetails fileUploadRequestDetails = generateUploadUrlProperResponse(); + PNFileUploadResult expectedResponse = pnFileUploadResult(); + PNPublishFileMessageResult publishFileMessageResult = new PNPublishFileMessageResult(expectedResponse.getTimetoken()); + + + when(generateUploadUrlFactory.create(any(), any())).thenReturn(TestRemoteAction.successful( + fileUploadRequestDetails)); + when(sendFileToS3Factory.create(any(), any(), any(), any())).thenReturn(TestRemoteAction.successful(null)); + PublishFileMessage publishFileMessage = AlwaysSuccessfulPublishFileMessage.create(publishFileMessageResult); + when(publishFileMessageBuilder.channel(any()).fileName(any()).fileId(any())) + .thenReturn(publishFileMessage); + + //when + try (FileInputStream fileInputStream = new FileInputStream(file)) { + sendFile(channel, file.getName(), fileInputStream).async( + (result, status) -> { + assertEquals(expectedResponse, result); + countDownLatch.countDown(); + } + ); + } + + assertTrue(countDownLatch.await(1, TimeUnit.SECONDS)); + } + @Test + public void async_publishFileMessageRetry() throws InterruptedException, IOException { + //given + CountDownLatch countDownLatch = new CountDownLatch(1); + File file = getTemporaryFile(filename); + FileUploadRequestDetails fileUploadRequestDetails = generateUploadUrlProperResponse(); + PNFileUploadResult expectedResponse = pnFileUploadResult(); + PNPublishFileMessageResult publishFileMessageResult = new PNPublishFileMessageResult(expectedResponse.getTimetoken()); + int numberOfRetries = 5; + + when(generateUploadUrlFactory.create(any(), any())).thenReturn(TestRemoteAction.successful( + fileUploadRequestDetails)); + when(sendFileToS3Factory.create(any(), any(), any(), any())).thenReturn(TestRemoteAction.successful(null)); + PublishFileMessage publishFileMessage = spy(FailingPublishFileMessage.create(publishFileMessageResult, numberOfRetries - 1)); + when(publishFileMessageBuilder.channel(any()).fileName(any()).fileId(any())) + .thenReturn(publishFileMessage); + + //when + try (FileInputStream fileInputStream = new FileInputStream(file)) { + sendFile(channel, file.getName(), fileInputStream, numberOfRetries).async( + (result, status) -> { + assertEquals(expectedResponse, result); + countDownLatch.countDown(); + } + ); + } + + //then + assertTrue(countDownLatch.await(1, TimeUnit.SECONDS)); + verify(publishFileMessage, times(numberOfRetries)).async(any()); + } + + @Test + public void sync_publishFileMessageRetry() throws InterruptedException, IOException, PubNubException { + //given + File file = getTemporaryFile(filename); + FileUploadRequestDetails fileUploadRequestDetails = generateUploadUrlProperResponse(); + PNFileUploadResult expectedResponse = pnFileUploadResult(); + PNPublishFileMessageResult publishFileMessageResult = new PNPublishFileMessageResult(expectedResponse.getTimetoken()); + int numberOfRetries = 5; + + when(generateUploadUrlFactory.create(any(), any())).thenReturn(TestRemoteAction.successful( + fileUploadRequestDetails)); + when(sendFileToS3Factory.create(any(), any(), any(), any())).thenReturn(TestRemoteAction.successful(null)); + PublishFileMessage publishFileMessage = spy(FailingPublishFileMessage.create(publishFileMessageResult, numberOfRetries - 1)); + when(publishFileMessageBuilder.channel(any()).fileName(any()).fileId(any())) + .thenReturn(publishFileMessage); + + //when + PNFileUploadResult result; + try (FileInputStream fileInputStream = new FileInputStream(file)) { + result = sendFile(channel, file.getName(), fileInputStream, numberOfRetries).sync(); + } + + //then + assertEquals(expectedResponse, result); + verify(publishFileMessage, times(numberOfRetries)).sync(); + + } + + private FileUploadRequestDetails generateUploadUrlProperResponse() { + return new FileUploadRequestDetails(200, + new PNBaseFile("id", "name"), + "url", + "GET", + Instant.now().plusSeconds(50).toString(), + new FormField("key", "value"), + Collections.emptyList()); + } + + private PNFileUploadResult pnFileUploadResult() { + return new PNFileUploadResult(1337L, 200, new PNBaseFile("id", "name")); + } + + @SneakyThrows + private SendFile sendFile(String channel, String fileName, InputStream inputStream, int numberOfRetries) { + return new SendFile(new SendFile.Builder.SendFileRequiredParams(channel, fileName, readBytes(inputStream), null), + generateUploadUrlFactory, + publishFileMessageBuilder, + sendFileToS3Factory, + Executors.newSingleThreadExecutor(), + numberOfRetries + ); + } + + private SendFile sendFile(String channel, String fileName, InputStream inputStream) { + return sendFile(channel, fileName, inputStream, 1); + } + + static class FailingPublishFileMessage extends PublishFileMessage { + + private final PNPublishFileMessageResult result; + private final int numberOfFailsBeforeSuccess; + private AtomicInteger numberOfFails = new AtomicInteger(0); + + + public static PublishFileMessage create(PNPublishFileMessageResult result, int numberOfFailsBeforeSuccess) { + return new FailingPublishFileMessage(result, numberOfFailsBeforeSuccess); + } + + + public FailingPublishFileMessage(PNPublishFileMessageResult result, + int numberOfFailsBeforeSuccess) { + super("channel", "fileName", "fileId", mock(PubNub.class), null, mock(RetrofitManager.class), new TokenManager()); + this.result = result; + this.numberOfFailsBeforeSuccess = numberOfFailsBeforeSuccess; + } + + @Override + public void async(@NotNull PNCallback callback) { + if (numberOfFails.getAndAdd(1) < numberOfFailsBeforeSuccess) { + callback.onResponse(null, PNStatus.builder().error(true).statusCode(400).build()); + } else { + callback.onResponse(result, PNStatus.builder().statusCode(200).build()); + } + } + + @Override + public @Nullable PNPublishFileMessageResult sync() throws PubNubException { + if (numberOfFails.getAndAdd(1) < numberOfFailsBeforeSuccess) { + throw PubNubException.builder().build(); + } + return result; + } + } + + static class AlwaysSuccessfulPublishFileMessage extends PublishFileMessage { + + private final PNPublishFileMessageResult result; + + public static PublishFileMessage create(PNPublishFileMessageResult result) { + PubNub pubNub = mock(PubNub.class); + RetrofitManager retrofitManager = mock(RetrofitManager.class); + return new AlwaysSuccessfulPublishFileMessage(result, + pubNub, + retrofitManager); + } + + AlwaysSuccessfulPublishFileMessage(PNPublishFileMessageResult result, + PubNub pubnubInstance, + RetrofitManager retrofitInstance) { + super("channel", "fileName", "fileId", mock(PubNub.class), null, mock(RetrofitManager.class), new TokenManager()); + this.result = result; + } + + @Nullable + @Override + public PNPublishFileMessageResult sync() throws PubNubException { + return result; + } + + @Override + public void async(@NotNull PNCallback callback) { + callback.onResponse(result, PNStatus.builder().statusCode(200).build()); + } + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/files/TestsWithFiles.java b/src/test/java/com/pubnub/api/endpoints/files/TestsWithFiles.java new file mode 100644 index 000000000..21a3153ee --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/files/TestsWithFiles.java @@ -0,0 +1,30 @@ +package com.pubnub.api.endpoints.files; + +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; + +@SuppressWarnings("UnstableApiUsage") +public interface TestsWithFiles { + TemporaryFolder folder = new TemporaryFolder(); + + TemporaryFolder getTemporaryFolder(); + + default File getTemporaryFile(String filename, String... content) { + try { + File file = getTemporaryFolder().newFile(filename); + ArrayList lines = new ArrayList<>(); + Collections.addAll(lines, content); + Files.write(file.toPath(), lines); + return file; + } catch (IOException ex) { + //fail + throw new RuntimeException(ex.getMessage(), ex); + } + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/files/UploadFileTest.java b/src/test/java/com/pubnub/api/endpoints/files/UploadFileTest.java new file mode 100644 index 000000000..631b4fad4 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/files/UploadFileTest.java @@ -0,0 +1,208 @@ +package com.pubnub.api.endpoints.files; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.server.files.FormField; +import com.pubnub.api.services.S3Service; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.mockito.stubbing.Answer; +import retrofit2.Call; +import retrofit2.Response; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Scanner; +import java.util.function.Supplier; + +import static com.pubnub.api.PubNubUtil.readBytes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class UploadFileTest implements TestsWithFiles { + private final S3Service s3Service = mock(S3Service.class); + private final ArgumentCaptor requestBodyArgumentCaptor = ArgumentCaptor.forClass(MultipartBody.class); + + @NotNull + protected static Answer> mockRetrofitSuccessfulCall(final Supplier block) { + return invocation -> { + final Call mockCall = mock(Call.class); + when(mockCall.execute()).thenAnswer(blockInvocation -> Response.success(block.get())); + return mockCall; + }; + } + + @NotNull + protected static Answer> mockRetrofitErrorCall(final Supplier block) { + return invocation -> { + final Call mockCall = mock(Call.class); + when(mockCall.execute()).thenAnswer(blockInvocation -> Response.error(400, ResponseBody.create(MediaType.get("application/xml"), block.get()))); + return mockCall; + }; + } + + @Test + public void keyIsFirstInMultipart() throws PubNubException, IOException { + //given + File file = getTemporaryFile("file.txt"); + try (FileInputStream fileInputStream = new FileInputStream(file)) { + UploadFile uploadFile = new UploadFile(s3Service, + file.getName(), + readBytes(fileInputStream), + null, + + new FormField("key", "keyValue"), + Collections.singletonList(new FormField("other", "otherValue")), + "https://s3.aws.com/bucket" + ); + + when(s3Service.upload(any(), any())).then(mockRetrofitSuccessfulCall(() -> null)); + + //when + uploadFile.sync(); + + //then + verify(s3Service, times(1)).upload(any(), requestBodyArgumentCaptor.capture()); + } + + MultipartBody capturedBody = requestBodyArgumentCaptor.getValue(); + + assertEquals("form-data; name=\"key\"", capturedBody.part(0).headers().get("Content-Disposition")); + assertPartExist("other", capturedBody.parts()); + assertPartExist("file", capturedBody.parts()); + + } + + @Test + public void contentTypeIsUsedForFileIfPresentInFormFields() throws PubNubException, IOException { + //given + File file = getTemporaryFile("file.txt"); + String contentTypeValue = "application/json"; + try (FileInputStream fileInputStream = new FileInputStream(file)) { + UploadFile uploadFile = new UploadFile(s3Service, + file.getName(), + readBytes(fileInputStream), + null, + new FormField("key", "keyValue"), + Collections.singletonList(new FormField("Content-Type", contentTypeValue)), + "https://s3.aws.com/bucket" + ); + + when(s3Service.upload(any(), any())).then(mockRetrofitSuccessfulCall(() -> null)); + + //when + uploadFile.sync(); + + //then + verify(s3Service, times(1)).upload(any(), requestBodyArgumentCaptor.capture()); + } + + MultipartBody capturedBody = requestBodyArgumentCaptor.getValue(); + + assertPartExist("file", capturedBody.parts()); + MultipartBody.Part filePart = getPart("file", capturedBody.parts()); + assertEquals(MediaType.get(contentTypeValue), filePart.body().contentType()); + } + + @Test + public void defaultContentTypeIsUsedForFileIfNotPresentInFormFields() throws PubNubException, IOException { + //given + File file = getTemporaryFile("file.txt"); + try (FileInputStream fileInputStream = new FileInputStream(file)) { + UploadFile uploadFile = new UploadFile(s3Service, + file.getName(), + readBytes(fileInputStream), + null, + new FormField("key", "keyValue"), + Collections.emptyList(), + "https://s3.aws.com/bucket" + ); + + when(s3Service.upload(any(), any())).then(mockRetrofitSuccessfulCall(() -> null)); + + //when + uploadFile.sync(); + + //then + verify(s3Service, times(1)).upload(any(), requestBodyArgumentCaptor.capture()); + } + + MultipartBody capturedBody = requestBodyArgumentCaptor.getValue(); + + assertPartExist("file", capturedBody.parts()); + MultipartBody.Part filePart = getPart("file", capturedBody.parts()); + assertEquals(MediaType.get("application/octet-stream"), filePart.body().contentType()); + } + + @Test + public void errorMessageIsCopiedFromS3XMLResponse() throws IOException{ + //given + File file = getTemporaryFile("file.txt"); + try (FileInputStream fileInputStream = new FileInputStream(file)) { + UploadFile uploadFile = new UploadFile(s3Service, + file.getName(), + readBytes(fileInputStream), + null, + new FormField("key", "keyValue"), + Collections.emptyList(), + "https://s3.aws.com/bucket" + ); + + when(s3Service.upload(any(), any())).then(mockRetrofitErrorCall(() -> readToString(UploadFileTest.class.getResourceAsStream("/entityTooLarge.xml")))); + + //when + try { + uploadFile.sync(); + Assert.fail("Exception expected"); + } catch (PubNubException ex) { + //then + Assert.assertEquals("Your proposed upload exceeds the maximum allowed size", ex.getErrormsg()); + } + } + } + + private String readToString(InputStream inputStream) { + Scanner s = new Scanner(inputStream).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + + private MultipartBody.Part getPart(String partName, List parts) { + MultipartBody.Part result = null; + for (MultipartBody.Part part : parts) { + if (part.headers().get("Content-Disposition").contains(partName)) { + result = part; + break; + } + } + return result; + } + + private void assertPartExist(String partName, List parts) { + MultipartBody.Part result = getPart(partName, parts); + if (result == null) { + fail("There's no part " + partName + " in parts " + parts); + } + } + + @Override + @Rule + public TemporaryFolder getTemporaryFolder() { + return folder; + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/BaseObjectApiTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/BaseObjectApiTest.java new file mode 100644 index 000000000..480554cac --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/BaseObjectApiTest.java @@ -0,0 +1,50 @@ +package com.pubnub.api.endpoints.objects_api; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.UUID; +import java.util.function.Supplier; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public abstract class BaseObjectApiTest { + private static final String TEST_PUBNUB_VERSION = "123"; + protected final String testSubscriptionKey = UUID.randomUUID().toString(); + protected final String testUUID = UUID.randomUUID().toString(); + + @Mock + private PNConfiguration configurationMock; + @Mock protected PubNub pubNubMock; + @Mock protected TelemetryManager telemetryManagerMock; + @Mock protected RetrofitManager retrofitManagerMock; + + @NotNull + protected static Answer> mockRetrofitSuccessfulCall(final Supplier block) { + return invocation -> { + final Call mockCall = mock(Call.class); + when(mockCall.execute()).thenAnswer(blockInvocation -> Response.success(block.get())); + return mockCall; + }; + } + + @Before + public void configureMocks() { + when(configurationMock.getSubscribeKey()).thenReturn(testSubscriptionKey); + when(configurationMock.getUuid()).thenReturn(testUUID); + when(pubNubMock.getConfiguration()).thenReturn(configurationMock); + when(pubNubMock.getVersion()).thenReturn(TEST_PUBNUB_VERSION); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadataTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadataTest.java new file mode 100644 index 000000000..4a1ea645a --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/channel/SetChannelMetadataTest.java @@ -0,0 +1,117 @@ +package com.pubnub.api.endpoints.objects_api.channel; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.BaseObjectApiTest; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.channel.PNSetChannelMetadataResult; +import com.pubnub.api.models.server.objects_api.SetChannelMetadataPayload; +import com.pubnub.api.services.ChannelMetadataService; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +public class SetChannelMetadataTest extends BaseObjectApiTest { + @Mock protected ChannelMetadataService channelMetadataServiceMock; + @Captor private ArgumentCaptor channelMetadataPayloadArgumentCaptor; + protected final String testChannelMetadataId = UUID.randomUUID().toString(); + + @Before + public void retrofitMocks() { + when(retrofitManagerMock.getChannelMetadataService()).thenReturn(channelMetadataServiceMock); + when(channelMetadataServiceMock.setChannelsMetadata(eq(testSubscriptionKey), eq(testChannelMetadataId), any(), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final PNSetChannelMetadataResult envelope = new PNSetChannelMetadataResult() { + + }; + return envelope; + })); + } + + @Test + public void setNameTest() throws PubNubException { + //given + final SetChannelMetadata setChannelMetadataUnderTest = SetChannelMetadata + .builder(pubNubMock, telemetryManagerMock, retrofitManagerMock, new TokenManager()) + .channel(testChannelMetadataId); + final String testName = RandomStringUtils.randomAlphabetic(20); + + //when + setChannelMetadataUnderTest + .name(testName) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)) + .setChannelsMetadata(eq(testSubscriptionKey), eq(testChannelMetadataId), + channelMetadataPayloadArgumentCaptor.capture(), any()); + + final SetChannelMetadataPayload capturedSetChannelMetadataPayload = channelMetadataPayloadArgumentCaptor + .getValue(); + assertEquals(testName, capturedSetChannelMetadataPayload.getName()); + } + + @Test + public void setDescriptionTest() throws PubNubException { + //given + final SetChannelMetadata setChannelMetadataUnderTest = SetChannelMetadata + .builder(pubNubMock, telemetryManagerMock, retrofitManagerMock, new TokenManager()) + .channel(testChannelMetadataId); + final String testDescription = RandomStringUtils.randomAlphabetic(20); + + //when + setChannelMetadataUnderTest + .description(testDescription) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)) + .setChannelsMetadata(eq(testSubscriptionKey), eq(testChannelMetadataId), + channelMetadataPayloadArgumentCaptor.capture(), any()); + + final SetChannelMetadataPayload capturedSetChannelMetadataPayload = channelMetadataPayloadArgumentCaptor + .getValue(); + assertEquals(testDescription, capturedSetChannelMetadataPayload.getDescription()); + } + + @Test + public void setCustomTest() throws PubNubException { + //given + final SetChannelMetadata setChannelMetadataUnderTest = SetChannelMetadata + .builder(pubNubMock, telemetryManagerMock, retrofitManagerMock, new TokenManager()) + .channel(testChannelMetadataId); + + final Map custom = new HashMap<>(); + custom.put("key1", RandomStringUtils.random(10)); + custom.put("key2", RandomStringUtils.random(10)); + + //when + setChannelMetadataUnderTest + .custom(custom) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)) + .setChannelsMetadata(eq(testSubscriptionKey), eq(testChannelMetadataId), + channelMetadataPayloadArgumentCaptor.capture(), any()); + + final SetChannelMetadataPayload capturedSetChannelMetadataPayload = channelMetadataPayloadArgumentCaptor + .getValue(); + assertNotNull(capturedSetChannelMetadataPayload.getCustom()); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembersTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembersTest.java new file mode 100644 index 000000000..78aa7d5e6 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/members/SetChannelMembersTest.java @@ -0,0 +1,200 @@ +package com.pubnub.api.endpoints.objects_api.members; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.BaseObjectApiTest; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.member.PNMembers; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID; +import com.pubnub.api.models.consumer.objects_api.member.PNUUID.UUIDWithCustom; +import com.pubnub.api.models.server.objects_api.PatchMemberPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import com.pubnub.api.services.ChannelMetadataService; +import org.apache.commons.lang3.RandomStringUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SetChannelMembersTest extends BaseObjectApiTest { + @Mock protected ChannelMetadataService channelMetadataServiceMock; + @Captor private ArgumentCaptor patchMembersPayloadArgumentCaptor; + + private final String testChannel = UUID.randomUUID().toString(); + + @Before + public void retrofitMocks() { + when(retrofitManagerMock.getChannelMetadataService()).thenReturn(channelMetadataServiceMock); + when(channelMetadataServiceMock.patchMembers(eq(testSubscriptionKey), eq(testChannel), any(), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final EntityArrayEnvelope envelope = new EntityArrayEnvelope<>(); + return envelope; + })); + when(channelMetadataServiceMock.getMembers(eq(testSubscriptionKey), eq(testChannel), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final EntityArrayEnvelope envelope = new EntityArrayEnvelope<>(); + return envelope; + })); + } + + @Test + public void setChanelMembersTest() throws PubNubException { + //given + final Map customObject = customObject(); + final Collection uuids = Arrays.asList( + PNUUID.uuid(randomUUIDId()), + PNUUID.uuidWithCustom(randomUUIDId(), customObject)); + final SetChannelMembers.Builder setChannelMembersUnderTest = SetChannelMembers.builder(pubNubMock, telemetryManagerMock, + retrofitManagerMock, + new TokenManager()); + + //when + setChannelMembersUnderTest + .channel(testChannel) + .uuids(uuids) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)).patchMembers(eq(testSubscriptionKey), + eq(testChannel), patchMembersPayloadArgumentCaptor.capture(), any()); + final PatchMemberPayload capturedPatchMemberPayload = patchMembersPayloadArgumentCaptor.getValue(); + final List uuidsPassedToService = extractUUIDs(capturedPatchMemberPayload.getSet()); + + final List expectedUUIDs = extractUUIDs(uuids); + + final List> passedCustomObjects = new ArrayList<>(); + for (final PNUUID it : capturedPatchMemberPayload.getSet()) { + if (it instanceof UUIDWithCustom) { + final UUIDWithCustom uuidWithCustom = (UUIDWithCustom) it; + final Object custom = uuidWithCustom.getCustom(); + if (custom instanceof Map) { + final Map stringStringMap = (Map) custom; + passedCustomObjects.add(stringStringMap); + } + } + } + + assertThat(passedCustomObjects, hasItems(customObject)); + assertThat(capturedPatchMemberPayload.getDelete(), is(empty())); + assertThat(uuidsPassedToService, containsInAnyOrder(expectedUUIDs.toArray())); + } + + @NotNull + private List extractUUIDs(final Collection set) { + final List uuidsPassedToService = new ArrayList<>(); + for (final PNUUID uuids : set) { + final String id = uuids.getUuid().getId(); + uuidsPassedToService.add(id); + } + return uuidsPassedToService; + } + + @Test + public void getChannelMembersTest() throws PubNubException { + //given + final GetChannelMembers.Builder getChannelMembersUnderTest = GetChannelMembers + .builder(pubNubMock, telemetryManagerMock, retrofitManagerMock, new TokenManager()); + + //when + getChannelMembersUnderTest + .channel(testChannel) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)) + .getMembers(eq(testSubscriptionKey), eq(testChannel), any()); + } + + @Test + public void manageChannelMembersTest() throws PubNubException { + //given + final Map customObject = customObject(); + final Collection uuidsToSet = Arrays.asList( + PNUUID.uuid(randomUUIDId()), + PNUUID.uuidWithCustom(randomUUIDId(), customObject)); + final Collection uuidsToRemove = Arrays.asList( + PNUUID.uuid(randomUUIDId()), + PNUUID.uuidWithCustom(randomUUIDId(), customObject)); + + final ManageChannelMembers.Builder manageChannelMembersUnderTest = ManageChannelMembers.builder(pubNubMock, + telemetryManagerMock, + retrofitManagerMock, new TokenManager()); + + //when + manageChannelMembersUnderTest + .channel(testChannel) + .set(uuidsToSet) + .remove(uuidsToRemove) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)).patchMembers(eq(testSubscriptionKey), + eq(testChannel), patchMembersPayloadArgumentCaptor.capture(), any()); + final PatchMemberPayload capturedPatchMemberPayload = patchMembersPayloadArgumentCaptor.getValue(); + final List uuidsToSetPassedToService = extractUUIDs(capturedPatchMemberPayload.getSet()); + final List uuidsToDeletePassedToService = extractUUIDs(capturedPatchMemberPayload.getDelete()); + + assertThat(uuidsToSetPassedToService, containsInAnyOrder(extractUUIDs(uuidsToSet).toArray())); + assertThat(uuidsToDeletePassedToService, containsInAnyOrder(extractUUIDs(uuidsToRemove).toArray())); + } + + @Test + public void removeChannelMembersTest() throws PubNubException { + //given + final Collection uuids = Arrays.asList( + PNUUID.uuid(randomUUIDId()), + PNUUID.uuid(randomUUIDId())); + + final RemoveChannelMembers.Builder removeChannelMembersUnderTest = RemoveChannelMembers.builder(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + + //when + removeChannelMembersUnderTest + .channel(testChannel) + .uuids(uuids) + .sync(); + + //then + verify(channelMetadataServiceMock, times(1)).patchMembers(eq(testSubscriptionKey), + eq(testChannel), patchMembersPayloadArgumentCaptor.capture(), any()); + final PatchMemberPayload capturedPatchMemberPayload = patchMembersPayloadArgumentCaptor.getValue(); + final List uuidsPassedToService = extractUUIDs(capturedPatchMemberPayload.getDelete()); + + final List expectedUUIDs = extractUUIDs(uuids); + + assertThat(capturedPatchMemberPayload.getSet(), is(empty())); + assertThat(uuidsPassedToService, containsInAnyOrder(expectedUUIDs.toArray())); + } + + + private static Map customObject() { + final Map custom = new HashMap<>(); + custom.put("key1", RandomStringUtils.random(10)); + custom.put("key2", RandomStringUtils.random(10)); + return custom; + } + + private static String randomUUIDId() { + return RandomStringUtils.randomAlphabetic(10); + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/memberships/SetMembershipsTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/memberships/SetMembershipsTest.java new file mode 100644 index 000000000..c7e09e6ce --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/memberships/SetMembershipsTest.java @@ -0,0 +1,196 @@ +package com.pubnub.api.endpoints.objects_api.memberships; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.BaseObjectApiTest; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembership; +import com.pubnub.api.models.server.objects_api.PatchMembershipPayload; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import com.pubnub.api.services.UUIDMetadataService; +import org.apache.commons.lang3.RandomStringUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership.ChannelWithCustom; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SetMembershipsTest extends BaseObjectApiTest { + @Mock protected UUIDMetadataService uuidMetadataServiceMock; + @Captor private ArgumentCaptor patchMembershipPayloadArgumentCaptor; + + @Before + public void retrofitMocks() { + when(retrofitManagerMock.getUuidMetadataService()).thenReturn(uuidMetadataServiceMock); + when(uuidMetadataServiceMock.patchMembership(eq(testSubscriptionKey), eq(testUUID), any(), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final EntityArrayEnvelope envelope = new EntityArrayEnvelope<>(); + return envelope; + })); + when(uuidMetadataServiceMock.getMemberships(eq(testSubscriptionKey), eq(testUUID), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final EntityArrayEnvelope envelope = new EntityArrayEnvelope<>(); + return envelope; + })); + } + + @Test + public void setMembershipTest() throws PubNubException { + //given + final Map customObject = customObject(); + final Collection channelMemberships = Arrays.asList( + PNChannelMembership.channel(randomChannelId()), + PNChannelMembership.channelWithCustom(randomChannelId(), customObject)); + final SetMemberships.Builder setMembershipsUnderTest = SetMemberships.builder(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + + //when + setMembershipsUnderTest + .channelMemberships(channelMemberships) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .patchMembership(eq(testSubscriptionKey), eq(testUUID), + patchMembershipPayloadArgumentCaptor.capture(), any()); + final PatchMembershipPayload capturedPatchMembershipPayload = patchMembershipPayloadArgumentCaptor.getValue(); + final List channelIdsToSetPassedToService = extractChannelIds(capturedPatchMembershipPayload.getSet()); + + final List expectedChannelIds = extractChannelIds(channelMemberships); + + final List> passedCustomObjects = new ArrayList<>(); + for (final PNChannelMembership it : capturedPatchMembershipPayload.getSet()) { + if (it instanceof ChannelWithCustom) { + final ChannelWithCustom channelWithCustom = (ChannelWithCustom) it; + final Object custom = channelWithCustom.getCustom(); + if (custom instanceof Map) { + final Map stringStringMap = (Map) custom; + passedCustomObjects.add(stringStringMap); + } + } + } + + assertThat(passedCustomObjects, hasItems(customObject)); + assertThat(capturedPatchMembershipPayload.getDelete(), is(empty())); + assertThat(channelIdsToSetPassedToService, containsInAnyOrder(expectedChannelIds.toArray())); + } + + @Test + public void getMembershipTest() throws PubNubException { + //given + final GetMemberships getMembershipsUnderTest = GetMemberships.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + + //when + getMembershipsUnderTest + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .getMemberships(eq(testSubscriptionKey), eq(testUUID), any()); + } + + @Test + public void manageMembershipTest() throws PubNubException { + //given + final Map customObject = customObject(); + final Collection channelMembershipsToSet = Arrays.asList( + PNChannelMembership.channel(randomChannelId()), + PNChannelMembership.channelWithCustom(randomChannelId(), customObject)); + final Collection channelMembershipsToRemove = Arrays.asList( + PNChannelMembership.channel(randomChannelId()), + PNChannelMembership.channelWithCustom(randomChannelId(), customObject)); + + final ManageMemberships.Builder manageMembershipsUnderTest = ManageMemberships.builder(pubNubMock, + telemetryManagerMock, + retrofitManagerMock, new TokenManager()); + + //when + manageMembershipsUnderTest + .set(channelMembershipsToSet) + .remove(channelMembershipsToRemove) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .patchMembership(eq(testSubscriptionKey), eq(testUUID), + patchMembershipPayloadArgumentCaptor.capture(), any()); + final PatchMembershipPayload capturedPatchMembershipPayload = patchMembershipPayloadArgumentCaptor.getValue(); + final List channelIdsToSetPassedToService = extractChannelIds(capturedPatchMembershipPayload.getSet()); + final List channelIdsToDeletePassedToService = extractChannelIds(capturedPatchMembershipPayload.getDelete()); + + assertThat(channelIdsToSetPassedToService, containsInAnyOrder(extractChannelIds(channelMembershipsToSet).toArray())); + assertThat(channelIdsToDeletePassedToService, containsInAnyOrder(extractChannelIds(channelMembershipsToRemove).toArray())); + } + + @Test + public void removeMembershipTest() throws PubNubException { + //given + final Collection channelMemberships = Arrays.asList( + PNChannelMembership.channel(randomChannelId()), + PNChannelMembership.channel(randomChannelId())); + + final RemoveMemberships.Builder removeMembershipsUnderTest = RemoveMemberships + .builder(pubNubMock, telemetryManagerMock, retrofitManagerMock, new TokenManager()); + + //when + removeMembershipsUnderTest + .channelMemberships(channelMemberships) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .patchMembership(eq(testSubscriptionKey), eq(testUUID), + patchMembershipPayloadArgumentCaptor.capture(), any()); + final PatchMembershipPayload capturedPatchMembershipPayload = patchMembershipPayloadArgumentCaptor.getValue(); + final List channelIdsPassedToService = extractChannelIds(capturedPatchMembershipPayload.getDelete()); + + final List expectedChannelIds = extractChannelIds(channelMemberships); + + assertThat(capturedPatchMembershipPayload.getSet(), is(empty())); + assertThat(channelIdsPassedToService, containsInAnyOrder(expectedChannelIds.toArray())); + } + + @NotNull + private static List extractChannelIds(final Collection channelMemberships) { + final List channelIdsPassedToService = new ArrayList<>(); + for (final PNChannelMembership channelMembership : channelMemberships) { + final String id = channelMembership.getChannel().getId(); + channelIdsPassedToService.add(id); + } + return channelIdsPassedToService; + } + + private static Map customObject() { + final Map custom = new HashMap<>(); + custom.put("key1", RandomStringUtils.random(10)); + custom.put("key2", RandomStringUtils.random(10)); + return custom; + } + + private static String randomChannelId() { + return RandomStringUtils.randomAlphabetic(10); + } + + +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsQueryParametersTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsQueryParametersTest.java new file mode 100644 index 000000000..be82d0866 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/utils/ObjectsQueryParametersTest.java @@ -0,0 +1,366 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.BaseObjectApiTest; +import com.pubnub.api.endpoints.objects_api.CompositeParameterEnricher; +import com.pubnub.api.endpoints.objects_api.ObjectApiEndpoint; +import com.pubnub.api.endpoints.objects_api.utils.Include.ChannelIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.CustomIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingChannelInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingCustomInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.HavingUUIDInclude; +import com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel; +import com.pubnub.api.endpoints.objects_api.utils.Include.PNUUIDDetailsLevel; +import com.pubnub.api.endpoints.objects_api.utils.Include.UUIDIncludeAware; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.HavingListCapabilites; +import com.pubnub.api.endpoints.objects_api.utils.ListCapabilities.ListCapabilitiesAware; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNPage; +import com.pubnub.api.models.server.objects_api.EntityArrayEnvelope; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; +import retrofit2.Call; +import retrofit2.Response; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_PAGINATION_NEXT_OUT_OF_BOUNDS; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_PAGINATION_PREV_OUT_OF_BOUNDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ObjectsQueryParametersTest extends BaseObjectApiTest { + + private final Random random = new Random(); + + abstract class ExampleTestEndpoint extends ObjectApiEndpoint implements + ListCapabilitiesAware, + CustomIncludeAware, + ChannelIncludeAware, + UUIDIncludeAware { + Map effectiveParams; + + public ExampleTestEndpoint(PubNub pubnubInstance, TelemetryManager telemetry, RetrofitManager retrofitInstance, CompositeParameterEnricher compositeParameterEnricher) { + super(pubnubInstance, telemetry, retrofitInstance, compositeParameterEnricher, new TokenManager()); + } + } + + class ExampleTestEndpointCommand extends ExampleTestEndpoint implements + HavingListCapabilites, + HavingCustomInclude, + HavingChannelInclude, + HavingUUIDInclude { + public ExampleTestEndpointCommand(PubNub pubnubInstance, TelemetryManager telemetry, RetrofitManager retrofitInstance) { + super(pubnubInstance, telemetry, retrofitInstance, CompositeParameterEnricher.createDefault()); + } + + @Override + public CompositeParameterEnricher getCompositeParameterEnricher() { + return super.getCompositeParameterEnricher(); + } + + @Override + public Call executeCommand(Map effectiveParams) throws PubNubException { + this.effectiveParams = new HashMap<>(effectiveParams); + final Call mockCall = mock(Call.class); + try { + when(mockCall.execute()).thenAnswer(invocation -> Response.success(new Object())); + } catch (IOException e) { + e.printStackTrace(); + } + return mockCall; + } + + @Override + protected Object createResponse(Response input) throws PubNubException { + return null; + } + + @Override + protected PNOperationType getOperationType() { + return null; + } + } + + @Test + public void noAdditionalParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, retrofitManagerMock); + + //when + exampleTestEndpoint + .sync(); + + //then + assertFalse(exampleTestEndpoint.effectiveParams.containsKey(Filter.FILTER_PARAM_NAME)); + } + + @Test + public void filterParametersSpecified() throws PubNubException { + //given + final String randomFilterExpression = RandomStringUtils.randomAlphabetic(20); + + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, retrofitManagerMock); + + //when + exampleTestEndpoint + .filter(randomFilterExpression) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Filter.FILTER_PARAM_NAME)); + assertEquals(randomFilterExpression, exampleTestEndpoint.effectiveParams.get(Filter.FILTER_PARAM_NAME)); + } + + @Test + public void limitParametersSpecified() throws PubNubException { + //given + final Integer randomLimit = random.nextInt(100); + + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, retrofitManagerMock); + + //when + exampleTestEndpoint + .limit(randomLimit) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Limiter.LIMIT_PARAM_NAME)); + assertEquals(randomLimit.toString(), exampleTestEndpoint.effectiveParams.get(Limiter.LIMIT_PARAM_NAME)); + } + + @Test + public void sortParametersSpecified() throws PubNubException { + //given + final PNSortKey.Key randomKey = PNSortKey.Key.values()[random.nextInt(PNSortKey.Key.values().length)]; + final PNSortKey.Dir randomDir = PNSortKey.Dir.values()[random.nextInt(PNSortKey.Dir.values().length)]; + final PNSortKey randomSortKey = PNSortKey.of(randomKey, randomDir); + + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + //when + exampleTestEndpoint + .sort(randomSortKey) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Sorter.SORT_PARAM_NAME)); + assertEquals(randomSortKey.toSortParameter(), exampleTestEndpoint.effectiveParams.get(Sorter.SORT_PARAM_NAME)); + } + + @Test + public void nextPageParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final EntityArrayEnvelope testEntityArrayEnvelope = new EntityArrayEnvelope() { + { + this.next = RandomStringUtils.randomAlphabetic(20); + this.prev = RandomStringUtils.randomAlphabetic(20); + } + }; + + final PNPage nextPage = testEntityArrayEnvelope.nextPage(); + + //when + exampleTestEndpoint + .page(nextPage) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Pager.START_PARAM_NAME)); + assertEquals(testEntityArrayEnvelope.getNext(), + exampleTestEndpoint.effectiveParams.get(Pager.START_PARAM_NAME)); + } + + @Test + public void previousPageParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final EntityArrayEnvelope testEntityArrayEnvelope = new EntityArrayEnvelope() { + { + this.next = RandomStringUtils.randomAlphabetic(20); + this.prev = RandomStringUtils.randomAlphabetic(20); + } + }; + + final PNPage prevoiousPage = testEntityArrayEnvelope.previousPage(); + + //when + exampleTestEndpoint + .page(prevoiousPage) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Pager.END_PARAM_NAME)); + assertEquals(testEntityArrayEnvelope.getPrev(), + exampleTestEndpoint.effectiveParams.get(Pager.END_PARAM_NAME)); + } + + @Test + public void includeTotalCountParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final Boolean randomBoolean = random.nextBoolean(); + + //when + exampleTestEndpoint + .includeTotalCount(randomBoolean) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(TotalCounter.COUNT_PARAM_NAME)); + assertEquals(randomBoolean.toString(), exampleTestEndpoint.effectiveParams.get(TotalCounter.COUNT_PARAM_NAME)); + } + + @Test + public void includeCustomParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final Boolean randomBoolean = random.nextBoolean(); + + //when + exampleTestEndpoint + .includeCustom(true) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Include.INCLUDE_PARAM_NAME)); + assertTrue(Arrays.asList(exampleTestEndpoint.effectiveParams.get(Include.INCLUDE_PARAM_NAME).split(",")) + .contains(Include.INCLUDE_CUSTOM_PARAM_VALUE)); + } + + @Test + public void includeChannelParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNChannelDetailsLevel channelDetailsLevel = PNChannelDetailsLevel.CHANNEL; + + //when + exampleTestEndpoint + .includeChannel(channelDetailsLevel) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Include.INCLUDE_PARAM_NAME)); + assertTrue(Arrays.asList(exampleTestEndpoint.effectiveParams.get(Include.INCLUDE_PARAM_NAME).split(",")) + .contains(Include.INCLUDE_CHANNEL_PARAM_VALUE)); + } + + @Test + public void includeChannelCustomParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNChannelDetailsLevel channelWithCustomDetailsLevel = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM; + + //when + exampleTestEndpoint + .includeChannel(channelWithCustomDetailsLevel) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Include.INCLUDE_PARAM_NAME)); + assertTrue(Arrays.asList(exampleTestEndpoint.effectiveParams.get(Include.INCLUDE_PARAM_NAME).split(",")) + .contains(Include.INCLUDE_CHANNEL_CUSTOM_PARAM_VALUE)); + } + + @Test + public void includeUUIDParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNUUIDDetailsLevel uuidDetailsLevel = PNUUIDDetailsLevel.UUID; + + //when + exampleTestEndpoint + .includeUUID(uuidDetailsLevel) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Include.INCLUDE_PARAM_NAME)); + assertTrue(Arrays.asList(exampleTestEndpoint.effectiveParams.get(Include.INCLUDE_PARAM_NAME).split(",")) + .contains(Include.INCLUDE_UUID_PARAM_VALUE)); + } + + @Test + public void includeUUIDCustomParametersSpecified() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNUUIDDetailsLevel uuidWithCustomDetailsLevel = PNUUIDDetailsLevel.UUID_WITH_CUSTOM; + + //when + exampleTestEndpoint + .includeUUID(uuidWithCustomDetailsLevel) + .sync(); + + //then + assertTrue(exampleTestEndpoint.effectiveParams.containsKey(Include.INCLUDE_PARAM_NAME)); + assertTrue(Arrays.asList(exampleTestEndpoint.effectiveParams.get(Include.INCLUDE_PARAM_NAME).split(",")) + .contains(Include.INCLUDE_UUID_CUSTOM_PARAM_VALUE)); + } + + @Test + public void validationErrorWhenPrevRequestedOnFirstPage() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNPage nonExistingPreviousPage = PNPage.previous(null); + + //when + try { + exampleTestEndpoint.page(nonExistingPreviousPage).sync(); + fail(); + } + //then + catch (PubNubException e) { + assertEquals(PNERR_PAGINATION_PREV_OUT_OF_BOUNDS, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validationErrorWhenNextRequestedOnFirstPage() throws PubNubException { + //given + final ExampleTestEndpoint exampleTestEndpoint = new ExampleTestEndpointCommand(pubNubMock, telemetryManagerMock, + retrofitManagerMock); + + final PNPage nonExistingNextPage = PNPage.next(null); + + //when + try { + exampleTestEndpoint.page(nonExistingNextPage).sync(); + fail(); + } + //then + catch (PubNubException e) { + assertEquals(PNERR_PAGINATION_NEXT_OUT_OF_BOUNDS, e.getPubnubError().getErrorCode()); + } + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/utils/SorterTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/utils/SorterTest.java new file mode 100644 index 000000000..394eb9adb --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/utils/SorterTest.java @@ -0,0 +1,37 @@ +package com.pubnub.api.endpoints.objects_api.utils; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +public class SorterTest { + @Test + public void sorterProducesCorrectQueryParam() { + //given + final Sorter sorterUnderTest = new Sorter(); + + final PNSortKey nameSortKey = PNSortKey.asc(PNSortKey.Key.NAME); + final PNSortKey updatedSortKey = PNSortKey.desc(PNSortKey.Key.UPDATED); + + sorterUnderTest.addSortKeys(Arrays.asList(nameSortKey, updatedSortKey)); + + //when + final Map enrichedParametersMap = sorterUnderTest.enrichParameters(Collections.emptyMap()); + + //then + final String parameterValue = enrichedParametersMap.get(Sorter.SORT_PARAM_NAME); + + final String expectedNameSortParamValue = nameSortKey.getKey().getFieldName() + ":" + + nameSortKey.getDir().getDir(); + final String expectedUpdatedParamValue = updatedSortKey.getKey().getFieldName() + ":" + + updatedSortKey.getDir().getDir(); + assertThat(parameterValue, allOf(containsString(expectedNameSortParamValue), + containsString(expectedUpdatedParamValue))); + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadataTest.java b/src/test/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadataTest.java new file mode 100644 index 000000000..d3e87c6c5 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/objects_api/uuid/SetUUIDMetadataTest.java @@ -0,0 +1,151 @@ +package com.pubnub.api.endpoints.objects_api.uuid; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.objects_api.BaseObjectApiTest; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.objects_api.uuid.PNSetUUIDMetadataResult; +import com.pubnub.api.models.server.objects_api.SetUUIDMetadataPayload; +import com.pubnub.api.services.UUIDMetadataService; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +public class SetUUIDMetadataTest extends BaseObjectApiTest { + @Mock protected UUIDMetadataService uuidMetadataServiceMock; + @Captor private ArgumentCaptor uuidMetadataPayloadArgumentCaptor; + + @Before + public void retrofitMocks() { + when(retrofitManagerMock.getUuidMetadataService()).thenReturn(uuidMetadataServiceMock); + when(uuidMetadataServiceMock.setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), any(), any())) + .thenAnswer(mockRetrofitSuccessfulCall(() -> { + final PNSetUUIDMetadataResult envelope = new PNSetUUIDMetadataResult() { + + }; + return envelope; + })); + } + + @Test + public void setNameTest() throws PubNubException { + //given + final SetUUIDMetadata setUUIDMetadataUnderTest = SetUUIDMetadata.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + final String testName = RandomStringUtils.randomAlphabetic(20); + + //when + setUUIDMetadataUnderTest + .name(testName) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), + uuidMetadataPayloadArgumentCaptor.capture(), any()); + + final SetUUIDMetadataPayload capturedSetUUIDMetadataPayload = uuidMetadataPayloadArgumentCaptor.getValue(); + assertEquals(testName, capturedSetUUIDMetadataPayload.getName()); + } + + @Test + public void setEmailTest() throws PubNubException { + //given + final SetUUIDMetadata setUUIDMetadataUnderTest = SetUUIDMetadata.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + final String testEmail = RandomStringUtils.randomAlphabetic(10) + "@example.com"; + + //when + setUUIDMetadataUnderTest + .email(testEmail) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), + uuidMetadataPayloadArgumentCaptor.capture(), any()); + + final SetUUIDMetadataPayload capturedSetUUIDMetadataPayload = uuidMetadataPayloadArgumentCaptor.getValue(); + assertEquals(testEmail, capturedSetUUIDMetadataPayload.getEmail()); + } + + @Test + public void setExternalIdTest() throws PubNubException { + //given + final SetUUIDMetadata setUUIDMetadataUnderTest = SetUUIDMetadata.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + final String testExternalId = UUID.randomUUID().toString(); + + //when + setUUIDMetadataUnderTest + .externalId(testExternalId) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), + uuidMetadataPayloadArgumentCaptor.capture(), any()); + + final SetUUIDMetadataPayload capturedSetUUIDMetadataPayload = uuidMetadataPayloadArgumentCaptor.getValue(); + assertEquals(testExternalId, capturedSetUUIDMetadataPayload.getExternalId()); + } + + @Test + public void setProfileUrlTest() throws PubNubException { + //given + final SetUUIDMetadata setUUIDMetadataUnderTest = SetUUIDMetadata.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + final String profileUrl = "http://" + RandomStringUtils.randomAlphabetic(5) + ".example.com"; + + //when + setUUIDMetadataUnderTest + .profileUrl(profileUrl) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), + uuidMetadataPayloadArgumentCaptor.capture(), any()); + + final SetUUIDMetadataPayload capturedSetUUIDMetadataPayload = uuidMetadataPayloadArgumentCaptor.getValue(); + assertEquals(profileUrl, capturedSetUUIDMetadataPayload.getProfileUrl()); + } + + @Test + public void setCustomTest() throws PubNubException { + //given + final SetUUIDMetadata setUUIDMetadataUnderTest = SetUUIDMetadata.create(pubNubMock, + telemetryManagerMock, retrofitManagerMock, new TokenManager()); + final Map custom = new HashMap<>(); + custom.put("key1", RandomStringUtils.random(10)); + custom.put("key2", RandomStringUtils.random(10)); + + //when + setUUIDMetadataUnderTest + .custom(custom) + .sync(); + + //then + verify(uuidMetadataServiceMock, times(1)) + .setUUIDsMetadata(eq(testSubscriptionKey), eq(testUUID), + uuidMetadataPayloadArgumentCaptor.capture(), any()); + + final SetUUIDMetadataPayload capturedSetUUIDMetadataPayload = uuidMetadataPayloadArgumentCaptor.getValue(); + assertNotNull(capturedSetUUIDMetadataPayload.getCustom()); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/presence/GetStateEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/presence/GetStateEndpointTest.java new file mode 100644 index 000000000..3b6f8da1a --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/presence/GetStateEndpointTest.java @@ -0,0 +1,267 @@ +package com.pubnub.api.endpoints.presence; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.google.gson.JsonElement; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.presence.PNGetStateResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + + +public class GetStateEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private GetState partialGetState; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialGetState = pubnub.getPresenceState(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testOneChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + + PNGetStateResult result = partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID" + ).sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("testChannel"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + } + + @Test + public void testOneChannelWithoutUUIDSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + + PNGetStateResult result = partialGetState.channels(Collections.singletonList("testChannel")).sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("testChannel"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + } + + + @Test(expected = PubNubException.class) + public void testFailedPayloadSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": \"age\" : 20, " + + "\"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID").sync(); + } + + @Test + public void testMultipleChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"ch1\": { " + + "\"age\" : 20, \"status\" : \"online\"}, \"ch2\": { \"age\": 100, \"status\": \"offline\" } " + + "}, \"service\": \"Presence\"}"))); + + PNGetStateResult result = partialGetState.channels(Arrays.asList("ch1", "ch2")).uuid("sampleUUID").sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("ch1"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + JsonElement ch2Data = result.getStateByUUID().get("ch2"); + assertEquals(pubnub.getMapper().elementToInt(ch2Data, "age"), 100); + assertEquals(pubnub.getMapper().elementToString(ch2Data, "status"), "offline"); + } + + @Test + public void testOneChannelGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"chcg1\": {" + + " \"age\" : 20, \"status\" : \"online\"}, \"chcg2\": { \"age\": 100, \"status\": \"offline\" " + + "} }, \"service\": \"Presence\"}"))); + + PNGetStateResult result = + partialGetState.channelGroups(Collections.singletonList("cg1")).uuid("sampleUUID").sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("chcg1"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + JsonElement ch2Data = result.getStateByUUID().get("chcg2"); + assertEquals(pubnub.getMapper().elementToInt(ch2Data, "age"), 100); + assertEquals(pubnub.getMapper().elementToString(ch2Data, "status"), "offline"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void testManyChannelGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"chcg1\": {" + + " \"age\" : 20, \"status\" : \"online\"}, \"chcg2\": { \"age\": 100, \"status\": \"offline\" " + + "} }, \"service\": \"Presence\"}"))); + + PNGetStateResult result = partialGetState.channelGroups(Arrays.asList("cg1", "cg2")).uuid("sampleUUID").sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("chcg1"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + JsonElement ch2Data = result.getStateByUUID().get("chcg2"); + assertEquals(pubnub.getMapper().elementToInt(ch2Data, "age"), 100); + assertEquals(pubnub.getMapper().elementToString(ch2Data, "status"), "offline"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1,cg2", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void testCombinationSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"chcg1\": {" + + " \"age\" : 20, \"status\" : \"online\"}, \"chcg2\": { \"age\": 100, \"status\": \"offline\" " + + "} }, \"service\": \"Presence\"}"))); + + PNGetStateResult result = + partialGetState.channels(Collections.singletonList("ch1")).channelGroups(Arrays.asList("cg1", "cg2")).uuid("sampleUUID").sync(); + + assert result != null; + + JsonElement ch1Data = result.getStateByUUID().get("chcg1"); + assertEquals(pubnub.getMapper().elementToInt(ch1Data, "age"), 20); + assertEquals(pubnub.getMapper().elementToString(ch1Data, "status"), "online"); + JsonElement ch2Data = result.getStateByUUID().get("chcg2"); + assertEquals(pubnub.getMapper().elementToInt(ch2Data, "age"), 100); + assertEquals(pubnub.getMapper().elementToString(ch2Data, "status"), "offline"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1,cg2", requests.get(0).queryParameter("channel-group").firstValue()); + + } + + @Test(expected = PubNubException.class) + public void testMissingChannelAndGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + partialGetState.uuid("sampleUUID").sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID").async(new PNCallback() { + @Override + public void onResponse(PNGetStateResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNGetState) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID").sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/sampleUUID")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\"}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + partialGetState.channels(Collections.singletonList("testChannel")).uuid("sampleUUID").sync(); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/presence/HereNowEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/presence/HereNowEndpointTest.java new file mode 100644 index 000000000..9adf41900 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/presence/HereNowEndpointTest.java @@ -0,0 +1,339 @@ +package com.pubnub.api.endpoints.presence; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.presence.PNHereNowResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class HereNowEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private HereNow partialHereNow; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialHereNow = pubnub.hereNow(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testMultipleChannelStateSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}}]},\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}},{\"uuid\":\"user3\",\"state\":{\"age\":30}}]}}}," + + "\"service\":\"Presence\"}"))); + + PNHereNowResult response = partialHereNow.channels(Arrays.asList("ch1", "ch2")).includeState(true).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 2); + assertEquals(response.getTotalOccupancy(), 3); + + assertEquals(response.getChannels().get("ch1").getChannelName(), "ch1"); + assertEquals(response.getChannels().get("ch1").getOccupancy(), 1); + assertEquals(response.getChannels().get("ch1").getOccupants().size(), 1); + assertEquals(response.getChannels().get("ch1").getOccupants().get(0).getUuid(), "user1"); + assertEquals(response.getChannels().get("ch1").getOccupants().get(0).getState().toString(), "{\"age\":10}"); + + assertEquals(response.getChannels().get("ch2").getChannelName(), "ch2"); + assertEquals(response.getChannels().get("ch2").getOccupancy(), 2); + assertEquals(response.getChannels().get("ch2").getOccupants().size(), 2); + assertEquals(response.getChannels().get("ch2").getOccupants().get(0).getUuid(), "user1"); + assertEquals(response.getChannels().get("ch2").getOccupants().get(0).getState().toString(), "{\"age\":10}"); + assertEquals(response.getChannels().get("ch2").getOccupants().get(1).getUuid(), "user3"); + assertEquals(response.getChannels().get("ch2").getOccupants().get(1).getState().toString(), "{\"age\":30}"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("state").firstValue()); + } + + @Test + public void testMultipleChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"}]}," + + "\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"},{\"uuid\":\"user3\"}]}}}," + + "\"service\":\"Presence\"}"))); + + PNHereNowResult response = partialHereNow.channels(Arrays.asList("ch1", "ch2")).includeState(true).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 2); + assertEquals(response.getTotalOccupancy(), 3); + + assertEquals(response.getChannels().get("ch1").getChannelName(), "ch1"); + assertEquals(response.getChannels().get("ch1").getOccupancy(), 1); + assertEquals(response.getChannels().get("ch1").getOccupants().size(), 1); + assertEquals(response.getChannels().get("ch1").getOccupants().get(0).getUuid(), "user1"); + assertNull(response.getChannels().get("ch1").getOccupants().get(0).getState()); + + assertEquals(response.getChannels().get("ch2").getChannelName(), "ch2"); + assertEquals(response.getChannels().get("ch2").getOccupancy(), 2); + assertEquals(response.getChannels().get("ch2").getOccupants().size(), 2); + assertEquals(response.getChannels().get("ch2").getOccupants().get(0).getUuid(), "user1"); + assertNull(response.getChannels().get("ch2").getOccupants().get(0).getState()); + assertEquals(response.getChannels().get("ch2").getOccupants().get(1).getUuid(), "user3"); + assertNull(response.getChannels().get("ch2").getOccupants().get(1).getState()); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("state").firstValue()); + } + + @Test + public void testMultipleChannelWithoutStateSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1,game2")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "{\"game1\": {\"uuids\": [\"a3ffd012-a3b9-478c-8705-64089f24d71e\"], \"occupancy\": 1}}, " + + "\"total_channels\": 1, \"total_occupancy\": 1}, \"service\": \"Presence\"}"))); + + PNHereNowResult response = partialHereNow.channels(Arrays.asList("game1", "game2")).includeState(false).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 1); + assertEquals(response.getTotalOccupancy(), 1); + + assertEquals(response.getChannels().get("game1").getChannelName(), "game1"); + assertEquals(response.getChannels().get("game1").getOccupancy(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().size(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().get(0).getUuid(), "a3ffd012-a3b9-478c-8705" + + "-64089f24d71e"); + assertNull(response.getChannels().get("game1").getOccupants().get(0).getState()); + + } + + @Test + public void testMultipleChannelWithoutStateUUIDsSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1,game2")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "{\"game1\": {\"occupancy\": 1}}, \"total_channels\": 1, \"total_occupancy\": 1}, " + + "\"service\": \"Presence\"}"))); + + PNHereNowResult response = + partialHereNow.channels(Arrays.asList("game1", "game2")).includeState(false).includeUUIDs(false).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 1); + assertEquals(response.getTotalOccupancy(), 1); + + assertEquals(response.getChannels().get("game1").getChannelName(), "game1"); + assertEquals(response.getChannels().get("game1").getOccupancy(), 1); + assertNull(response.getChannels().get("game1").getOccupants()); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("disable_uuids").firstValue()); + } + + @Test + public void testSingularChannelWithoutStateUUIDsSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"occupancy\": 3}"))); + + PNHereNowResult response = + partialHereNow.channels(Arrays.asList("game1")).includeState(false).includeUUIDs(false).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 1); + assertEquals(response.getTotalOccupancy(), 3); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("disable_uuids").firstValue()); + + } + + @Test + public void testSingularChannelWithoutStateSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"uuids\": [\"a3ffd012-a3b9-478c-8705-64089f24d71e\"], \"occupancy\": 1}"))); + + PNHereNowResult response = partialHereNow.channels(Arrays.asList("game1")).includeState(false).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 1); + assertEquals(response.getTotalOccupancy(), 1); + assertEquals(response.getChannels().size(), 1); + assertEquals(response.getChannels().get("game1").getOccupancy(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().size(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().get(0).getUuid(), "a3ffd012-a3b9-478c-8705" + + "-64089f24d71e"); + assertEquals(response.getChannels().get("game1").getOccupants().get(0).getState(), null); + + } + + @Test + public void testSingularChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\",\"service\":\"Presence\"," + + "\"uuids\":[{\"uuid\":\"a3ffd012-a3b9-478c-8705-64089f24d71e\",\"state\":{\"age\":10}}]," + + "\"occupancy\":1}"))); + + PNHereNowResult response = partialHereNow.channels(Arrays.asList("game1")).includeState(true).sync(); + + assert response != null; + + assertEquals(response.getTotalChannels(), 1); + assertEquals(response.getTotalOccupancy(), 1); + assertEquals(response.getChannels().size(), 1); + assertEquals(response.getChannels().get("game1").getOccupancy(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().size(), 1); + assertEquals(response.getChannels().get("game1").getOccupants().get(0).getUuid(), "a3ffd012-a3b9-478c-8705" + + "-64089f24d71e"); + assertEquals(response.getChannels().get("game1").getOccupants().get(0).getState().toString(), "{\"age\":10}"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("state").firstValue()); + } + + @Test + public void testSingularChannelAndGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/game1")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\",\"payload\":{\"channels\":{}, " + + "\"total_channels\":0, \"total_occupancy\":0},\"service\":\"Presence\"}"))); + + PNHereNowResult response = + partialHereNow.channelGroups(Arrays.asList("grp1")).channels(Arrays.asList("game1")).includeState(true).sync(); + + assert response != null; + + assertEquals(response.getTotalOccupancy(), 0); + } + + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}}]},\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}},{\"uuid\":\"user3\",\"state\":{\"age\":30}}]}}}," + + "\"service\":\"Presence\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialHereNow.channels(Arrays.asList("ch1", "ch2")).includeState(true).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}}]},\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}},{\"uuid\":\"user3\",\"state\":{\"age\":30}}]}}}," + + "\"service\":\"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + partialHereNow.async(new PNCallback() { + @Override + public void onResponse(PNHereNowResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNHereNowOperation) { + atomic.incrementAndGet(); + } + + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}}]},\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}},{\"uuid\":\"user3\",\"state\":{\"age\":30}}]}}}," + + "\"service\":\"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + partialHereNow.channels(Arrays.asList("ch1", "ch2")).includeState(true).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub_key/mySubscribeKey/channel/ch1,ch2")) + .willReturn(aResponse().withBody("{\"status\":200,\"message\":\"OK\"," + + "\"payload\":{\"total_occupancy\":3,\"total_channels\":2," + + "\"channels\":{\"ch1\":{\"occupancy\":1,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}}]},\"ch2\":{\"occupancy\":2,\"uuids\":[{\"uuid\":\"user1\"," + + "\"state\":{\"age\":10}},{\"uuid\":\"user3\",\"state\":{\"age\":30}}]}}}," + + "\"service\":\"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + partialHereNow.channels(Arrays.asList("ch1", "ch2")).includeState(true).sync(); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/presence/LeaveTest.java b/src/test/java/com/pubnub/api/endpoints/presence/LeaveTest.java new file mode 100644 index 000000000..2003fae4a --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/presence/LeaveTest.java @@ -0,0 +1,194 @@ +package com.pubnub.api.endpoints.presence; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.consumer.PNStatus; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + + +public class LeaveTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private Leave instance; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + RetrofitManager retrofitManager = new RetrofitManager(pubnub); + instance = new Leave(pubnub, null, retrofitManager, new TokenManager()); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void subscribeChannelSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channels(Arrays.asList("coolChannel")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + } + + @Test + public void subscribeChannelsSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + + @Test + public void subscribeChannelsWithGroupSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).channelGroups(Arrays.asList("cg1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void subscribeChannelsWithGroupASync() throws PubNubException { + + final AtomicBoolean statusArrived = new AtomicBoolean(); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel,coolChannel2/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).channelGroups(Arrays.asList("cg1")).async(new PNCallback() { + @Override + public void onResponse(Boolean result, @NotNull PNStatus status) { + assertEquals(status.getAffectedChannels().get(0), "coolChannel"); + assertEquals(status.getAffectedChannels().get(1), "coolChannel2"); + assertEquals(status.getAffectedChannelGroups().get(0), "cg1"); + statusArrived.set(true); + } + }); + + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(statusArrived, + org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void subscribeGroupsSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channelGroups(Arrays.asList("cg1", "cg2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1,cg2", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void subscribeGroupSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.channelGroups(Arrays.asList("cg1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testMissingChannelAndGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + instance.sync(); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instance.sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instance.sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/coolChannel/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + instance.channels(Arrays.asList("coolChannel")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/presence/SetStateEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/presence/SetStateEndpointTest.java new file mode 100644 index 000000000..a082d2038 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/presence/SetStateEndpointTest.java @@ -0,0 +1,329 @@ +package com.pubnub.api.endpoints.presence; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.models.consumer.presence.PNSetStateResult; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; + + +public class SetStateEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private SetState partialSetState; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialSetState = pubnub.setPresenceState(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void applyStateForChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void applyStateForSomebodyElseChannelSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/someoneElseUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).uuid( + "someoneElseUUID").sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void applyStateForChannelsSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel," + + "testChannel2/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = + partialSetState.channels(Arrays.asList("testChannel", "testChannel2")).state(myState).sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void applyStateForChannelGroupSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = partialSetState.channelGroups(Collections.singletonList("cg1")).state(myState).sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + } + + @Test + public void applyStateForChannelGroupsSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/,/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = partialSetState.channelGroups(Arrays.asList("cg1", "cg2")).state(myState).sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1,cg2", requests.get(0).queryParameter("channel-group").firstValue()); + + } + + @Test + public void applyStateForMixSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + PNSetStateResult result = + partialSetState.channels(Collections.singletonList("ch1")).channelGroups(Arrays.asList("cg1", "cg2")).state(myState).sync(); + + assert result != null; + + assertEquals(pubnub.getMapper().elementToInt(result.getState(), "age"), 20); + assertEquals(pubnub.getMapper().elementToString(result.getState(), "status"), "online"); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + } + + @Test(expected = PubNubException.class) + public void applyNon200Sync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", matching("%7B%22status%22%3A%22oneline%22%2C%22age%22%3A20%7D")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}").withStatus(400))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + partialSetState.channels(Collections.singletonList("ch1")).channelGroups(Arrays.asList("cg1", "cg2")).state(myState).sync(); + } + + @Test(expected = PubNubException.class) + public void missingStateSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + partialSetState.channels(Collections.singletonList("testChannel")).sync(); + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .withQueryParam("state", equalToJson("{\"age\":20}")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + pubnub.getConfiguration().setSubscribeKey(null); + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + pubnub.getConfiguration().setSubscribeKey(""); + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).sync(); + } + + @Test(expected = PubNubException.class) + public void testChannelAndGroupMissingSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"payload\": { \"age\" : " + + "20, \"status\" : \"online\" }, \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + partialSetState.state(myState).sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPayloadSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/testChannel/uuid/myUUID/data")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("state", matching("%7B%22age%22%3A20%7D")) + .willReturn(aResponse().withBody("{ \"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + Map myState = new HashMap<>(); + myState.put("age", 20); + + partialSetState.channels(Collections.singletonList("testChannel")).state(myState).sync(); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/presence/WhereNowEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/presence/WhereNowEndpointTest.java new file mode 100644 index 000000000..a58f3aeab --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/presence/WhereNowEndpointTest.java @@ -0,0 +1,246 @@ +package com.pubnub.api.endpoints.presence; + + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.WhereNowCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.presence.PNWhereNowResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class WhereNowEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private WhereNow partialWhereNow; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + partialWhereNow = pubnub.whereNow(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testSyncSuccess() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + PNWhereNowResult response = partialWhereNow.sync(); + + assert response != null; + + assertThat(response.getChannels(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test + public void testSyncSuccessCustomUUID() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/customUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + PNWhereNowResult response = partialWhereNow.uuid("customUUID").sync(); + + assert response != null; + + assertThat(response.getChannels(), org.hamcrest.Matchers.contains("a", "b")); + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithString() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[zimp]}, \"service\": \"Presence\"}"))); + + partialWhereNow.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithoutJSON() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "zimp}, \"service\": \"Presence\"}"))); + + partialWhereNow.sync(); + } + + @Test(expected = PubNubException.class) + public void testSyncBrokenWithout200() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse() + .withStatus(404) + .withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": [\"a\",\"b\"]}," + + " \"service\": \"Presence\"}"))); + + partialWhereNow.sync(); + } + + @Test + public void testAsyncSuccess() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + partialWhereNow.async(new WhereNowCallback() { + + @Override + public void onResponse(@Nullable PNWhereNowResult result, @NotNull PNStatus status) { + assert result != null; + assertThat(result.getChannels(), org.hamcrest.Matchers.contains("a", "b")); + atomic.incrementAndGet(); + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testAsyncBrokenWithString() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[zimp]}, \"service\": \"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + partialWhereNow.async(new WhereNowCallback() { + + @Override + public void onResponse(@Nullable PNWhereNowResult result, @NotNull PNStatus status) { + atomic.incrementAndGet(); + } + + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testAsyncBrokenWithoutJSON() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "zimp}, \"service\": \"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + partialWhereNow.async(new WhereNowCallback() { + + @Override + public void onResponse(@Nullable PNWhereNowResult result, @NotNull PNStatus status) { + atomic.incrementAndGet(); + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testAsyncBrokenWithout200() throws IOException, PubNubException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse() + .withStatus(400) + .withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": [\"a\",\"b\"]}," + + " \"service\": \"Presence\"}"))); + + final AtomicInteger atomic = new AtomicInteger(0); + partialWhereNow.async(new WhereNowCallback() { + + @Override + public void onResponse(PNWhereNowResult result, @NotNull PNStatus status) { + atomic.incrementAndGet(); + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testIsAuthRequiredSuccessSync() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + partialWhereNow.sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + partialWhereNow.sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"payload\": {\"channels\": " + + "[\"a\",\"b\"]}, \"service\": \"Presence\"}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + partialWhereNow.sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPayloadSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/uuid/myUUID")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"}"))); + + partialWhereNow.sync(); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/pubsub/PublishTest.java b/src/test/java/com/pubnub/api/endpoints/pubsub/PublishTest.java new file mode 100644 index 000000000..a3596a166 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/pubsub/PublishTest.java @@ -0,0 +1,465 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.*; + +public class PublishTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private Publish instance; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + instance = pubnub.publish(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testFireSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.fire().channel("coolChannel").message("hi").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("true", requests.get(0).queryParameter("norep").firstValue()); + assertEquals("0", requests.get(0).queryParameter("store").firstValue()); + } + + @Test + public void testNoRepSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").replicate(false).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("true", requests.get(0).queryParameter("norep").firstValue()); + } + + @Test + public void testRepDefaultSuccessSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hirep").replicate(false).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("true", requests.get(0).queryParameter("norep").firstValue()); + } + + @Test + public void testSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + } + + @Test + public void testSuccessSequenceSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").sync(); + instance.channel("coolChannel").message("hi").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(2, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("1", requests.get(0).queryParameter("seqn").firstValue()); + assertEquals("2", requests.get(1).queryParameter("seqn").firstValue()); + + + } + + @Test + public void testSuccessPostSync() throws PubNubException, InterruptedException, UnsupportedEncodingException { + stubFor(post(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").usePOST(true).message(Arrays.asList("m1", "m2")).sync(); + + List requests = findAll(postRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("[\"m1\",\"m2\"]", new String(requests.get(0).getBody(), "UTF-8")); + } + + @Test + public void testSuccessStoreFalseSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").shouldStore(false).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("0", requests.get(0).queryParameter("store").firstValue()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + } + + @Test + public void testSuccessStoreTrueSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").shouldStore(true).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("1", requests.get(0).queryParameter("store").firstValue()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + } + + @Test + public void testSuccessMetaSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + //.withQueryParam("meta", matching("%5B%22m1%22%2C%22m2%22%5D")) + .withQueryParam("meta", equalToJson("[\"m1\",\"m2\"]")) + .withQueryParam("store", matching("0")) + .withQueryParam("seqn", matching("1")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").meta(Arrays.asList("m1", "m2")).shouldStore(false).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void testSuccessAuthKeySync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setAuthKey("authKey"); + instance.channel("coolChannel").message("hi").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("authKey", requests.get(0).queryParameter("auth").firstValue()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test + public void testSuccessIntSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/10")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(10).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test + public void testSuccessArraySync() throws PubNubException, InterruptedException { + stubFor(get(urlEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%5B%22a%22%2C%22b%22%2C%22c%22" + + "%5D?pnsdk=PubNub-Java-Unified/suchJava&requestid=PubNubRequestId&seqn=1&uuid=myUUID")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(Arrays.asList("a", "b", "c")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + } + + @Test + public void testSuccessArrayEncryptedSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22HFP7V6bDwBLrwc1t8Rnrog%3D" + + "%3D%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setCipherKey("testCipher"); + instance.channel("coolChannel").message(Arrays.asList("m1", "m2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + } + + @Test + public void testSuccessPostEncryptedSync() throws PubNubException, InterruptedException { + stubFor(post(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setCipherKey("testCipher"); + + instance.channel("coolChannel").usePOST(true).message(Arrays.asList("m1", "m2")).sync(); + + List requests = findAll(postRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertEquals("\"HFP7V6bDwBLrwc1t8Rnrog==\"", new String(requests.get(0).getBody(), Charset.forName("UTF-8"))); + } + + @Test + public void testSuccessHashMapSync() throws PubNubException, InterruptedException { + Map params = new HashMap<>(); + params.put("a", 10); + params.put("z", "test"); + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22a%22%3A10%2C%22z%22%3A" + + "%22test%22%7D")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(params).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test + public void testSuccessPOJOSync() throws PubNubException, InterruptedException { + TestPojo testPojo = new TestPojo("10", "20"); + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22field1%22%3A%2210%22" + + "%2C%22field2%22%3A%2220%22%7D")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(testPojo).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test + public void testJSONObject() throws PubNubException, InterruptedException { + JSONObject testMessage = new JSONObject(); + testMessage.put("hi", "test"); + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%7B%22hi%22%3A%22test%22%7D")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(testMessage.toMap()).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test + public void testJSONList() throws PubNubException, InterruptedException { + JSONArray testMessage = new JSONArray(); + testMessage.put("hi"); + testMessage.put("hi2"); + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%5B%22hi%22%2C%22hi2%22%5D")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(testMessage.toList()).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + + } + + @Test(expected = PubNubException.class) + public void testMissingChannel() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.message("hi").sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptyChannel() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.message("hi").channel("").sync(); + } + + @Test(expected = PubNubException.class) + public void testMissingMessage() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").sync(); + } + + @Test + public void testOperationTypeSuccessAsync() throws IOException, PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + instance.async(new PNCallback() { + @Override + public void onResponse(PNPublishResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() == PNOperationType.PNPublishOperation) { + atomic.incrementAndGet(); + } + + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instance.channel("coolChannel").message("hirep").sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instance.channel("coolChannel").message("hirep").sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPublishKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setPublishKey(null); + instance.channel("coolChannel").message("hirep").sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptyPublishKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + pubnub.getConfiguration().setPublishKey(""); + instance.channel("coolChannel").message("hirep").sync(); + } + + @Test(expected = PubNubException.class) + public void testInvalidMessage() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message(new Object()).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertNull(requests.get(0).queryParameter("norep")); + } + + @Test(expected = PubNubException.class) + public void testInvalidMeta() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hirep%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").meta(new Object()).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myUUID", requests.get(0).queryParameter("uuid").firstValue()); + assertNull(requests.get(0).queryParameter("norep")); + } + + @Test + public void testTTLShouldStoryDefaultSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").ttl(10).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("10", requests.get(0).queryParameter("ttl").firstValue()); + } + + @Test + public void testTTLShouldStoreFalseSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/publish/myPublishKey/mySubscribeKey/0/coolChannel/0/%22hi%22")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"14598111595318003\"]"))); + + instance.channel("coolChannel").message("hi").shouldStore(false).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("0", requests.get(0).queryParameter("store").firstValue()); + assertFalse(requests.get(0).queryParameter("ttl").isPresent()); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/pubsub/SignalTest.java b/src/test/java/com/pubnub/api/endpoints/pubsub/SignalTest.java new file mode 100644 index 000000000..341f80a48 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/pubsub/SignalTest.java @@ -0,0 +1,256 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.github.tomakehurst.wiremock.http.RequestMethod; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.models.consumer.PNPublishResult; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import okhttp3.HttpUrl; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_CHANNEL_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_MESSAGE_MISSING; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SignalTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubNub; + + @Before + public void beforeEach() throws IOException { + pubNub = this.createPubNubInstance(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubNub.destroy(); + pubNub = null; + wireMockRule.stop(); + } + + @Test + public void testSignalGetSuccessSync() throws PubNubException { + stubFor(get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"1000\"]"))); + + Map payload = new HashMap<>(); + payload.put("text", "hello"); + + pubNub.signal() + .channel("coolChannel") + .message(payload) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + + HttpUrl httpUrl = HttpUrl.parse(request.getAbsoluteUrl()); + String decodedSignalPayload = null; + if (httpUrl != null) { + decodedSignalPayload = httpUrl.pathSegments().get(httpUrl.pathSize() - 1); + } + assertEquals(pubNub.getMapper().toJson(payload), decodedSignalPayload); + } + + @Test + public void testSignalGetSuccessAsync() { + + String payload = UUID.randomUUID().toString(); + + stubFor(get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"1000\"]"))); + + final AtomicBoolean success = new AtomicBoolean(); + + pubNub.signal() + .channel("coolChannel") + .message(payload) + .async(new PNCallback() { + @Override + public void onResponse(PNPublishResult result, @NotNull PNStatus status) { + assertFalse(status.isError()); + assertEquals(PNOperationType.PNSignalOperation, status.getOperation()); + assertEquals("1000", result.getTimetoken().toString()); + success.set(true); + } + }); + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilTrue(success); + + } + + @Test + public void testSignalSuccessReceive() { + + stubFor(get(urlMatching("/v2/subscribe/mySubscribeKey/coolChannel/0.*")) + .willReturn(aResponse().withBody("{\"m\":[{\"c\":\"coolChannel\",\"f\":\"0\",\"i\":\"uuid\"," + + "\"d\":\"hello\",\"e\":1,\"p\":{\"t\":1000,\"r\":1},\"k\":\"mySubscribeKey\"," + + "\"b\":\"coolChannel\"}],\"t\":{\"r\":\"56\",\"t\":1000}}"))); + + AtomicBoolean success = new AtomicBoolean(); + + pubNub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + throw new RuntimeException("Should never receive a message"); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + assertEquals("coolChannel", signal.getChannel()); + assertEquals("hello", signal.getMessage().getAsString()); + assertEquals("uuid", signal.getPublisher()); + success.set(true); + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubNub.subscribe() + .channels(Collections.singletonList("coolChannel")) + .execute(); + + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .untilTrue(success); + } + + @Test + public void testSignalFailNoChannel() { + try { + pubNub.signal() + .message(UUID.randomUUID().toString()) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERROBJ_CHANNEL_MISSING.getMessage(), e.getPubnubError().getMessage()); + } + } + + @Test + public void testSignalFailNoMessage() { + try { + pubNub.signal() + .channel(UUID.randomUUID().toString()) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERROBJ_MESSAGE_MISSING.getMessage(), e.getPubnubError().getMessage()); + } + } + + @Test + public void testSignalTelemetryParam() throws PubNubException { + stubFor(get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"1000\"]"))); + + stubFor(get(urlMatching("/time/0.*")) + .willReturn(aResponse().withBody("[1000]"))); + + pubNub.signal() + .channel("coolChannel") + .message(UUID.randomUUID().toString()) + .sync(); + + pubNub.time() + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/time/0.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertTrue(request.queryParameter("l_sig").isPresent()); + } + + @Test + public void testSignalHttpMethod() throws PubNubException { + stubFor(get(urlMatching("/signal/myPublishKey/mySubscribeKey/0/coolChannel.*")) + .willReturn(aResponse().withBody("[1,\"Sent\",\"1000\"]"))); + + pubNub.signal() + .channel("coolChannel") + .message(UUID.randomUUID().toString()) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/signal.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals(RequestMethod.GET, request.getMethod()); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.java new file mode 100644 index 000000000..0cfebdbb5 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/pubsub/SubscribeEndpointTest.java @@ -0,0 +1,302 @@ +package com.pubnub.api.endpoints.pubsub; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.SubscribeEnvelope; +import com.pubnub.api.models.server.SubscribeMessage; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SubscribeEndpointTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private Subscribe instance; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + RetrofitManager retrofitManager = new RetrofitManager(pubnub); + instance = new Subscribe(pubnub, retrofitManager, new TokenManager()); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void subscribeChannelSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + SubscribeEnvelope subscribeEnvelope = instance.channels(Arrays.asList("coolChannel")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("1", subscribeEnvelope.getMetadata().getRegion()); + assertTrue(subscribeEnvelope.getMetadata().getTimetoken().equals(14607577960932487L)); + + assertEquals(1, subscribeEnvelope.getMessages().size()); + SubscribeMessage subscribeMessage = subscribeEnvelope.getMessages().get(0); + assertEquals("4", subscribeMessage.getShard()); + assertEquals("0", subscribeMessage.getFlags()); + assertEquals("coolChannel", subscribeMessage.getChannel()); + assertEquals("coolChan-bnel", subscribeMessage.getSubscriptionMatch()); + assertEquals("sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f", subscribeMessage.getSubscribeKey()); + assertEquals("Client-g5d4g", subscribeMessage.getIssuingClientId()); + assertEquals("{\"text\":\"Enter Message Here\"}", subscribeMessage.getPayload().toString()); + } + + @Test + public void subscribeChannelsSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void subscribeChannelsAuthSync() throws PubNubException { + + pubnub.getConfiguration().setAuthKey("authKey"); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals("authKey", requests.get(0).queryParameter("auth").firstValue()); + assertEquals(1, requests.size()); + } + + @Test + public void subscribeChannelsWithGroupSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).channelGroups(Arrays.asList("cg1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void subscribeGroupsSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channelGroups(Arrays.asList("cg1", "cg2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1,cg2", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void subscribeGroupSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channelGroups(Arrays.asList("cg1")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + } + + @Test + public void subscribeWithTimeTokenSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channelGroups(Arrays.asList("cg1")).timetoken(1337L).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + assertEquals("1337", requests.get(0).queryParameter("tt").firstValue()); + } + + @Test + public void subscribeWithFilter() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("filter-expr", matching("this=1&that=cool")) + .withQueryParam("channel-group", matching("cg1")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channelGroups(Arrays.asList("cg1")).filterExpression("this=1&that=cool").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + } + + @Test + public void subscribeWithRegion() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channelGroups(Arrays.asList("cg1")).region("10").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("cg1", requests.get(0).queryParameter("channel-group").firstValue()); + assertEquals("10", requests.get(0).queryParameter("tr").firstValue()); + } + + @Test(expected = PubNubException.class) + public void subscribeMissingChannelAndGroupSync() throws PubNubException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.sync(); + } + + @Test(expected = PubNubException.class) + public void testNullSubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubKeySync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + } + + @Test + public void stopAndReconnect() throws PubNubException { + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/coolChannel,coolChannel2/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + pubnub.disconnect(); + pubnub.reconnect(); + instance.channels(Arrays.asList("coolChannel", "coolChannel2")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(2, requests.size()); + } + + @Test + public void testSuccessIncludeState() { + Map state = new HashMap<>(); + state.put("CH1", "this-is-channel1"); + state.put("CH2", "this-is-channel2"); + + pubnub.getConfiguration().setPresenceTimeout(123); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch2/0")) + .willReturn(aResponse().withStatus(200))); + + try { + instance.channels(Arrays.asList("ch1", "ch2")).state(state).sync(); + } catch (PubNubException e) { + e.printStackTrace(); + } + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + LoggedRequest request = requests.get(0); + assertEquals("myUUID", request.queryParameter("uuid").firstValue()); + assertEquals("123", request.queryParameter("heartbeat").firstValue()); + assertEquals("{\"CH2\":\"this-is-channel2\",\"CH1\":\"this-is-channel1\"}", + request.queryParameter("state").firstValue()); + + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/pubsub/TestPojo.java b/src/test/java/com/pubnub/api/endpoints/pubsub/TestPojo.java new file mode 100644 index 000000000..cc45caea5 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/pubsub/TestPojo.java @@ -0,0 +1,14 @@ +package com.pubnub.api.endpoints.pubsub; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Created by Max on 9/8/16. + */ +@AllArgsConstructor +@Getter +class TestPojo { + private String field1; + private String field2; +} diff --git a/src/test/java/com/pubnub/api/endpoints/push/ListPushProvisionsTest.java b/src/test/java/com/pubnub/api/endpoints/push/ListPushProvisionsTest.java new file mode 100644 index 000000000..b11f31ba4 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/push/ListPushProvisionsTest.java @@ -0,0 +1,259 @@ +package com.pubnub.api.endpoints.push; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.push.PNPushListProvisionsResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class ListPushProvisionsTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private ListPushProvisions instance; + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + instance = pubnub.auditPushChannelProvisions(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testAppleSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("apns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("news").isPresent()); + } + + @Test + public void testGoogleSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.GCM).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("news").isPresent()); + } + + @Test + public void testFirebaseSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.FCM).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("news").isPresent()); + } + + @Test + public void testMicrosoftSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("news").isPresent()); + } + + @Test + public void testApns2SuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS2).topic("news").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("development", requests.get(0).queryParameter("environment").firstValue()); + assertEquals("news", requests.get(0).queryParameter("topic").firstValue()); + assertFalse(requests.get(0).queryParameter("type").isPresent()); + } + + @Test + public void testIsAuthRequiredSuccess() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + instance.deviceId("niceDevice").pushType(PNPushType.APNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccess() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS).async(new PNCallback() { + @Override + public void onResponse(PNPushListProvisionsResult result, @NotNull PNStatus status) { + if (status != null + && status.getOperation() == PNOperationType.PNPushNotificationEnabledChannelsOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubscribeKey() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instance.deviceId("niceDevice").pushType(PNPushType.APNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubscribeKey() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPushType() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").sync(); + } + + @Test(expected = PubNubException.class) + public void testNullDeviceId() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.pushType(PNPushType.MPNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptyDeviceIdRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("").pushType(PNPushType.MPNS).sync(); + } + + @Test + public void testApns2NoTopic() { + try { + instance.deviceId("niceDevice").pushType(PNPushType.APNS2).sync(); + } catch (PubNubException e) { + Assert.assertEquals(e.getPubnubError(), PubNubErrorBuilder.PNERROBJ_PUSH_TOPIC_MISSING); + } + } + + @Test + public void testApns2DefaultEnvironment() throws PubNubException { + stubFor(get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("[\"ch1\", \"ch2\", \"ch3\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS2).topic("news").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + + assertEquals("development", requests.get(0).queryParameter("environment").firstValue()); + } + + @Test + public void testPushTypeNames() { + String expectedName = "gcm"; + Assert.assertEquals(PNPushType.GCM.toString(), PNPushType.FCM.toString()); + Assert.assertEquals(PNPushType.GCM + "", PNPushType.FCM + ""); + Assert.assertEquals(expectedName, PNPushType.GCM + ""); + Assert.assertEquals(expectedName, PNPushType.FCM + ""); + Assert.assertEquals(expectedName, PNPushType.GCM.toString()); + Assert.assertEquals(expectedName, PNPushType.FCM.toString()); + + Assert.assertEquals("mpns", PNPushType.MPNS.toString()); + Assert.assertEquals("apns", PNPushType.APNS.toString()); + Assert.assertEquals("apns2", PNPushType.APNS2.toString()); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/push/ModifyPushChannelsForDeviceTest.java b/src/test/java/com/pubnub/api/endpoints/push/ModifyPushChannelsForDeviceTest.java new file mode 100644 index 000000000..4ad02c949 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/push/ModifyPushChannelsForDeviceTest.java @@ -0,0 +1,632 @@ +package com.pubnub.api.endpoints.push; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNPushType; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult; +import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant; +import com.pubnub.api.models.consumer.push.PNPushAddChannelResult; +import com.pubnub.api.models.consumer.push.PNPushRemoveAllChannelsResult; +import com.pubnub.api.models.consumer.push.PNPushRemoveChannelResult; + +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class ModifyPushChannelsForDeviceTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(this.PORT), false); + + private PubNub pubnub; + private RemoveAllPushChannelsForDevice instance; + private AddChannelsToPush instanceAdd; + private RemoveChannelsFromPush instanceRemove; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + instance = pubnub.removeAllPushNotificationsFromDeviceWithPushToken(); + instanceAdd = pubnub.addPushNotificationsOnChannels(); + instanceRemove = pubnub.removePushNotificationsFromChannels(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testAppleSuccessSyncRemoveAll() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("apns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testGoogleSuccessSyncRemoveAll() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.GCM).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testFirebaseSuccessSyncRemoveAll() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.FCM).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testMicrosoftSuccessSyncRemoveAll() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testApns2SuccessSyncRemoveAll() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").pushType(PNPushType.APNS2).topic("news").sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("development", requests.get(0).queryParameter("environment").firstValue()); + assertEquals("news", requests.get(0).queryParameter("topic").firstValue()); + assertFalse(requests.get(0).queryParameter("type").isPresent()); + } + + @Test + public void testIsAuthRequiredSuccessRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).async( + new PNCallback() { + @Override + public void onResponse(PNPushRemoveAllChannelsResult result, @NotNull PNStatus status) { + if (status != null + && status.getOperation() == PNOperationType.PNRemoveAllPushNotificationsOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + + @Test(expected = PubNubException.class) + public void testNullSubscribeKeyRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptySubscribeKeyRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instance.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testNullPushTypeRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("niceDevice").sync(); + } + + @Test(expected = PubNubException.class) + public void testNullDeviceIdRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.pushType(PNPushType.MPNS).sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptyDeviceIdRemoveAll() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice/remove")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instance.deviceId("").pushType(PNPushType.MPNS).sync(); + } + + @Test + public void testAddAppleSuccessSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.APNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("apns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testAddGoogleSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.GCM) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testAddFirebaseSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.FCM) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testAddMicrosoftSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testAddApns2SuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.APNS2) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .topic("topic") + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("development", requests.get(0).queryParameter("environment").firstValue()); + assertEquals("topic", requests.get(0).queryParameter("topic").firstValue()); + assertFalse(requests.get(0).queryParameter("type").isPresent()); + } + + @Test + public void testIsAuthRequiredSuccessAdd() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessAdd() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .async(new PNCallback() { + @Override + public void onResponse(PNPushAddChannelResult result, @NotNull PNStatus status) { + if (status != null && status.getOperation() + == PNOperationType.PNPushNotificationEnabledChannelsOperation) { + atomic.incrementAndGet(); + } + } + }); + + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubscribeKeyAdd() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testEmptySubscribeKeyAdd() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("ch1,ch2,ch3", requests.get(0).queryParameter("add").firstValue()); + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testNullPushTypeAdd() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice") + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + } + + @Test(expected = PubNubException.class) + public void testNullDeviceIdAdd() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + } + + @Test(expected = PubNubException.class) + public void testEmptyDeviceIdAdd() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("").pushType(PNPushType.MPNS) + .channels(Arrays.asList("ch1", "ch2", "ch3")) + .sync(); + } + + @Test(expected = PubNubException.class) + public void testMissingChannelsAdd() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceAdd.deviceId("niceDevice").pushType(PNPushType.MPNS) + .sync(); + } + + + @Test + public void testRemoveAppleSuccessSync() throws PubNubException, InterruptedException { + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.APNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("apns", requests.get(0).queryParameter("type").firstValue()); + assertEquals("chr1,chr2,chr3", requests.get(0).queryParameter("remove").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testRemoveGoogleSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.GCM) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertEquals("chr1,chr2,chr3", requests.get(0).queryParameter("remove").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testRemoveFirebaseSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.FCM) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("gcm", requests.get(0).queryParameter("type").firstValue()); + assertEquals("chr1,chr2,chr3", requests.get(0).queryParameter("remove").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testRemoveMicrosoftSuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("mpns", requests.get(0).queryParameter("type").firstValue()); + assertEquals("chr1,chr2,chr3", requests.get(0).queryParameter("remove").firstValue()); + assertFalse(requests.get(0).queryParameter("environment").isPresent()); + assertFalse(requests.get(0).queryParameter("topic").isPresent()); + } + + @Test + public void testRemoveApns2SuccessSync() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v2/push/sub-key/mySubscribeKey/devices-apns2/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.APNS2).topic("news") + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("chr1,chr2,chr3", requests.get(0).queryParameter("remove").firstValue()); + assertEquals("development", requests.get(0).queryParameter("environment").firstValue()); + assertEquals("news", requests.get(0).queryParameter("topic").firstValue()); + assertFalse(requests.get(0).queryParameter("type").isPresent()); + } + + @Test + public void testIsAuthRequiredSuccessRemove() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setAuthKey("myKey"); + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test + public void testOperationTypeSuccessRemove() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + final AtomicInteger atomic = new AtomicInteger(0); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).async(new PNCallback() { + @Override + public void onResponse(PNPushRemoveChannelResult result, @NotNull PNStatus status) { + if (status != null + && status.getOperation() == PNOperationType.PNRemovePushNotificationsFromChannelsOperation) { + atomic.incrementAndGet(); + } + } + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test(expected = PubNubException.class) + public void testNullSubscribeKeyRemove() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(null); + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testEmptySubscribeKeyRemove() throws IOException, PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + pubnub.getConfiguration().setSubscribeKey(""); + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + List requests = findAll(getRequestedFor(urlMatching("/.*"))); + assertEquals(1, requests.size()); + assertEquals("myKey", requests.get(0).queryParameter("auth").firstValue()); + } + + @Test(expected = PubNubException.class) + public void testNullPushType() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + } + + @Test(expected = PubNubException.class) + public void testNullDeviceId() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + } + + @Test(expected = PubNubException.class) + public void testEmptyDeviceId() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("").pushType(PNPushType.MPNS) + .channels(Arrays.asList("chr1", "chr2", "chr3")).sync(); + + } + + @Test(expected = PubNubException.class) + public void testMissingChannels() throws PubNubException, InterruptedException { + + stubFor(get(urlPathEqualTo("/v1/push/sub-key/mySubscribeKey/devices/niceDevice")) + .willReturn(aResponse().withBody("[1, \"Modified Channels\"]"))); + + instanceRemove.deviceId("niceDevice").pushType(PNPushType.MPNS).sync(); + + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.java b/src/test/java/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.java new file mode 100644 index 000000000..c353728ea --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/push/PushPayloadHelperHelperTest.java @@ -0,0 +1,632 @@ +package com.pubnub.api.endpoints.push; + +import com.pubnub.api.PubNub; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNPushEnvironment; +import com.pubnub.api.models.consumer.push.payload.PushPayloadHelper; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PushPayloadHelperHelperTest extends TestHarness { + + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + } + + @Test + public void testPayloads_Missing() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + Map map = pushPayloadHelper.build(); + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testPayloads_Null() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + pushPayloadHelper.setApnsPayload(null); + pushPayloadHelper.setCommonPayload(null); + pushPayloadHelper.setFcmPayload(null); + pushPayloadHelper.setMpnsPayload(null); + + Map map = pushPayloadHelper.build(); + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testPayloads_Empty() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + pushPayloadHelper.setApnsPayload(new PushPayloadHelper.APNSPayload()); + pushPayloadHelper.setCommonPayload(new HashMap<>()); + pushPayloadHelper.setFcmPayload(new PushPayloadHelper.FCMPayload()); + pushPayloadHelper.setMpnsPayload(new PushPayloadHelper.MPNSPayload()); + + Map map = pushPayloadHelper.build(); + + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testApple_Empty() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.APNSPayload apnsPayload = new PushPayloadHelper.APNSPayload(); + + apnsPayload.setAps(new PushPayloadHelper.APNSPayload.APS()); + apnsPayload.setApns2Configurations(new ArrayList<>()); + + Map map = pushPayloadHelper.build(); + + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testApple_Valid() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.APNSPayload apnsPayload = new PushPayloadHelper.APNSPayload(); + + PushPayloadHelper.APNSPayload.APS aps = new PushPayloadHelper.APNSPayload.APS(); + aps.setAlert("alert"); + aps.setBadge(5); + + HashMap customMap = new HashMap<>(); + customMap.put("key_1", "1"); + customMap.put("key_2", 2); + customMap.put("key_3", null); + + apnsPayload.setAps(aps); + apnsPayload.setApns2Configurations(new ArrayList<>()); + apnsPayload.setCustom(customMap); + + pushPayloadHelper.setApnsPayload(apnsPayload); + + Map map = pushPayloadHelper.build(); + + HashMap pnApnsDataMap = (HashMap) map.get("pn_apns"); + + Assert.assertEquals("1", pnApnsDataMap.get("key_1")); + Assert.assertEquals(2, pnApnsDataMap.get("key_2")); + Assert.assertFalse(pnApnsDataMap.containsKey("key_3")); + + HashMap apsMap = (HashMap) pnApnsDataMap.get("aps"); + Assert.assertEquals("alert", apsMap.get("alert")); + Assert.assertEquals(5, apsMap.get("badge")); + + List pushList = (List) pnApnsDataMap.get("pn_push"); + Assert.assertEquals(0, pushList.size()); + } + + @Test + public void testApple_Aps() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.APNSPayload apnsPayload = new PushPayloadHelper.APNSPayload(); + apnsPayload.setAps(new PushPayloadHelper.APNSPayload.APS() + .setAlert("alert") + .setBadge(5) + ); + pushPayloadHelper.setApnsPayload(apnsPayload); + + HashMap customMap = new HashMap<>(); + customMap.put("key_1", null); + customMap.put("key_2", "2"); + apnsPayload.setCustom(customMap); + + Map map = pushPayloadHelper.build(); + + HashMap pnApnsDataMap = (HashMap) map.get("pn_apns"); + HashMap apsMap = (HashMap) pnApnsDataMap.get("aps"); + + Assert.assertEquals("alert", apsMap.get("alert")); + Assert.assertEquals(5, apsMap.get("badge")); + Assert.assertFalse(apsMap.containsKey("sound")); + + Assert.assertFalse(pnApnsDataMap.containsKey("pn_push")); + + Assert.assertFalse(pnApnsDataMap.containsKey("key_1")); + Assert.assertEquals("2", pnApnsDataMap.get("key_2")); + + } + + @Test + public void testApple_PnPushArray() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target1 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setEnvironment(PNPushEnvironment.DEVELOPMENT) + .setTopic("topic_1"); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target2 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setEnvironment(PNPushEnvironment.PRODUCTION); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target3 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setEnvironment(PNPushEnvironment.PRODUCTION) + .setTopic("topic_3") + .setExcludeDevices(Arrays.asList("ex_1", "ex_2")); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target4 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setEnvironment(null) + .setTopic(null) + .setExcludeDevices(null); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target5 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setEnvironment(PNPushEnvironment.PRODUCTION) + .setTopic("topic_5") + .setExcludeDevices(Arrays.asList()); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target6 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target() + .setTopic("topic_6") + .setExcludeDevices(null); + + PushPayloadHelper.APNSPayload.APNS2Configuration.Target target7 = + new PushPayloadHelper.APNSPayload.APNS2Configuration.Target(); + + PushPayloadHelper.APNSPayload.APNS2Configuration apns2Config1 = new PushPayloadHelper.APNSPayload.APNS2Configuration() + .setCollapseId("collapse_1") + .setExpiration("exp_1") + .setVersion("v1") + .setTargets(null); + + PushPayloadHelper.APNSPayload.APNS2Configuration apns2Config2 = new PushPayloadHelper.APNSPayload.APNS2Configuration() + .setCollapseId("collapse_2") + .setExpiration("exp_2") + .setVersion("v2") + .setTargets(new ArrayList<>()); + + PushPayloadHelper.APNSPayload.APNS2Configuration apns2Config3 = new PushPayloadHelper.APNSPayload.APNS2Configuration() + .setCollapseId(null) + .setExpiration("") + .setVersion("v3") + .setTargets(Arrays.asList( + target1, + target2, + target3, + target4, + target5, + target6, + target7 + )); + + PushPayloadHelper.APNSPayload.APNS2Configuration apns2Config4 = new PushPayloadHelper.APNSPayload.APNS2Configuration() + .setCollapseId(null) + .setExpiration(null) + .setVersion(null); + + PushPayloadHelper.APNSPayload.APNS2Configuration apns2Config5 = new PushPayloadHelper.APNSPayload.APNS2Configuration(); + + PushPayloadHelper.APNSPayload apnsPayload = new PushPayloadHelper.APNSPayload(); + + List apns2Configurations = new ArrayList<>(); + apns2Configurations.add(apns2Config1); + apns2Configurations.add(apns2Config2); + apns2Configurations.add(apns2Config3); + apns2Configurations.add(apns2Config4); + apns2Configurations.add(apns2Config5); + apnsPayload.setApns2Configurations(apns2Configurations); + + pushPayloadHelper.setApnsPayload(apnsPayload); + + Map map = pushPayloadHelper.build(); + + + HashMap apnsMap = (HashMap) map.get("pn_apns"); + List> pnPushList = (List>) apnsMap.get("pn_push"); + + Assert.assertEquals(3, pnPushList.size()); + + HashMap pushItemMap1 = pnPushList.get(0); + + Assert.assertEquals("exp_1", pnPushList.get(0).get("expiration")); + Assert.assertEquals("collapse_1", pnPushList.get(0).get("collapse_id")); + Assert.assertEquals("v1", pnPushList.get(0).get("version")); + Assert.assertFalse(pnPushList.get(0).containsKey("targets")); + + Assert.assertEquals("exp_2", pnPushList.get(1).get("expiration")); + Assert.assertEquals("collapse_2", pnPushList.get(1).get("collapse_id")); + Assert.assertEquals("v2", pnPushList.get(1).get("version")); + Assert.assertFalse(pnPushList.get(1).containsKey("targets")); + + Assert.assertEquals("", pnPushList.get(2).get("expiration")); + Assert.assertFalse(pnPushList.get(2).containsKey("collapse_id")); + Assert.assertEquals("v3", pnPushList.get(2).get("version")); + Assert.assertTrue(pnPushList.get(2).containsKey("targets")); + + + List> pnTargetsMap = (List>) pnPushList.get(2).get("targets"); + + Assert.assertEquals("development", pnTargetsMap.get(0).get("environment")); + Assert.assertEquals("topic_1", pnTargetsMap.get(0).get("topic")); + Assert.assertFalse(pnTargetsMap.get(0).containsKey("excludeDevices")); + + Assert.assertEquals("production", pnTargetsMap.get(1).get("environment")); + Assert.assertFalse(pnTargetsMap.get(1).containsKey("topic_2")); + Assert.assertFalse(pnTargetsMap.get(1).containsKey("excludeDevices")); + + Assert.assertEquals("production", pnTargetsMap.get(2).get("environment")); + Assert.assertEquals("topic_3", pnTargetsMap.get(2).get("topic")); + Assert.assertEquals("ex_1", ((List) pnTargetsMap.get(2).get("excluded_devices")).get(0)); + Assert.assertEquals("ex_2", ((List) pnTargetsMap.get(2).get("excluded_devices")).get(1)); + + Assert.assertEquals("production", pnTargetsMap.get(3).get("environment")); + Assert.assertFalse(pnTargetsMap.get(3).containsKey("topic_5")); + Assert.assertFalse(pnTargetsMap.get(3).containsKey("excludeDevices")); + + Assert.assertFalse(pnTargetsMap.get(4).containsKey("environment")); + Assert.assertEquals("topic_6", pnTargetsMap.get(4).get("topic")); + Assert.assertFalse(pnTargetsMap.get(4).containsKey("environment")); + } + + @Test + public void testApple_Aps_Empty() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.APNSPayload apnsPayload = new PushPayloadHelper.APNSPayload(); + apnsPayload.setAps(new PushPayloadHelper.APNSPayload.APS()); + pushPayloadHelper.setApnsPayload(apnsPayload); + + Map map = pushPayloadHelper.build(); + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testCommonPayload_Valid() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + HashMap commonPayload = new HashMap<>(); + commonPayload.put("common_key_1", 1); + commonPayload.put("common_key_2", "2"); + commonPayload.put("common_key_3", true); + pushPayloadHelper.setCommonPayload(commonPayload); + + Map map = pushPayloadHelper.build(); + + Assert.assertEquals(map.get("common_key_1"), 1); + Assert.assertEquals(map.get("common_key_2"), "2"); + Assert.assertEquals(map.get("common_key_3"), true); + } + + @Test + public void testCommonPayload_Invalid() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + HashMap commonPayload = new HashMap<>(); + commonPayload.put("common_key_1", null); + commonPayload.put("common_key_2", null); + commonPayload.put("common_key_3", null); + pushPayloadHelper.setCommonPayload(commonPayload); + + Map map = pushPayloadHelper.build(); + + Assert.assertTrue(map.isEmpty()); + } + + @Test + public void testGoogle_Valid_1() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + + fcmPayload.setNotification( + new PushPayloadHelper.FCMPayload.Notification() + .setBody("Notification body") + .setImage(null) + .setTitle("") + .setClickAction("FOO_ACTION") + ); + + HashMap customFcmPayload = new HashMap<>(); + customFcmPayload.put("a", "a"); + customFcmPayload.put("b", 1); + customFcmPayload.put("c", null); + fcmPayload.setCustom(customFcmPayload); + + HashMap dataFcmPayload = new HashMap<>(); + dataFcmPayload.put("data_1", "a"); + dataFcmPayload.put("data_2", 1); + dataFcmPayload.put("data_3", null); + fcmPayload.setData(dataFcmPayload); + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + + Assert.assertNotNull(pnFcmMap); + + HashMap pnFcmDataMap = (HashMap) pnFcmMap.get("data"); + HashMap pnFcmNotificationsMap = (HashMap) pnFcmMap.get("notification"); + + Assert.assertNotNull(pnFcmDataMap); + Assert.assertNotNull(pnFcmNotificationsMap); + + Assert.assertEquals(pnFcmMap.get("a"), "a"); + Assert.assertEquals(pnFcmMap.get("b"), 1); + Assert.assertEquals(pnFcmMap.get("c"), null); + + Assert.assertEquals(pnFcmDataMap.get("data_1"), "a"); + Assert.assertEquals(pnFcmDataMap.get("data_2"), 1); + Assert.assertEquals(pnFcmDataMap.get("data_3"), null); + + Assert.assertEquals(pnFcmNotificationsMap.get("body"), "Notification body"); + Assert.assertEquals(pnFcmNotificationsMap.get("image"), null); + Assert.assertEquals(pnFcmNotificationsMap.get("title"), ""); + Assert.assertEquals(pnFcmNotificationsMap.get("click_action"), "FOO_ACTION"); + } + + @Test + public void testGoogle_Empty() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + + + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + + Assert.assertNull(pnFcmMap); + } + + @Test + public void testGoogle_EmptyNotification() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload.Notification notification = new PushPayloadHelper.FCMPayload.Notification(); + HashMap customMap = new HashMap<>(); + customMap.put("key_1", "1"); + customMap.put("key_2", 2); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + fcmPayload.setNotification(notification); + fcmPayload.setCustom(customMap); + + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + + + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + HashMap pnFcmNotificationMap = (HashMap) pnFcmMap.get("notification"); + + Assert.assertNull(pnFcmNotificationMap); + } + + @Test + public void testGoogle_EmptyData() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + HashMap dataMap = new HashMap<>(); + fcmPayload.setData(dataMap); + + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + + + Assert.assertNull(pnFcmMap); + } + + @Test + public void testGoogle_Valid_2() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + + HashMap dataMap = new HashMap<>(); + dataMap.put("key_1", "value_1"); + dataMap.put("key_2", 2); + dataMap.put("key_3", true); + dataMap.put("key_4", ""); + dataMap.put("key_5", null); + fcmPayload.setData(dataMap); + + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + HashMap pnFcmDataMap = (HashMap) pnFcmMap.get("data"); + + Assert.assertNotNull(pnFcmDataMap); + Assert.assertFalse(pnFcmDataMap.isEmpty()); + Assert.assertTrue(pnFcmDataMap.containsKey("key_1")); + Assert.assertTrue(pnFcmDataMap.containsKey("key_2")); + Assert.assertTrue(pnFcmDataMap.containsKey("key_3")); + Assert.assertTrue(pnFcmDataMap.containsKey("key_4")); + Assert.assertTrue(pnFcmDataMap.containsKey("key_5")); + Assert.assertNotNull(pnFcmDataMap.get("key_1")); + Assert.assertNotNull(pnFcmDataMap.get("key_2")); + Assert.assertNotNull(pnFcmDataMap.get("key_3")); + Assert.assertNotNull(pnFcmDataMap.get("key_4")); + Assert.assertNull(pnFcmDataMap.get("key_5")); + } + + @Test + public void testGoogle_Custom() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.FCMPayload fcmPayload = new PushPayloadHelper.FCMPayload(); + + HashMap customMap = new HashMap<>(); + customMap.put("key_1", "value_1"); + customMap.put("key_2", 2); + customMap.put("key_3", true); + customMap.put("key_4", ""); + customMap.put("key_5", null); + fcmPayload.setCustom(customMap); + + pushPayloadHelper.setFcmPayload(fcmPayload); + + Map map = pushPayloadHelper.build(); + + HashMap pnFcmMap = (HashMap) map.get("pn_gcm"); + + Assert.assertNotNull(pnFcmMap); + Assert.assertFalse(pnFcmMap.isEmpty()); + Assert.assertTrue(pnFcmMap.containsKey("key_1")); + Assert.assertTrue(pnFcmMap.containsKey("key_2")); + Assert.assertTrue(pnFcmMap.containsKey("key_3")); + Assert.assertTrue(pnFcmMap.containsKey("key_4")); + Assert.assertFalse(pnFcmMap.containsKey("key_5")); + Assert.assertNotNull(pnFcmMap.get("key_1")); + Assert.assertNotNull(pnFcmMap.get("key_2")); + Assert.assertNotNull(pnFcmMap.get("key_3")); + Assert.assertNotNull(pnFcmMap.get("key_4")); + Assert.assertNull(pnFcmMap.get("key_5")); + } + + @Test + public void testMicrosoft_Missing() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.MPNSPayload mpnsPayload = new PushPayloadHelper.MPNSPayload(); + + mpnsPayload.setBackContent("Back Content"); + mpnsPayload.setBackTitle("Back Title"); + mpnsPayload.setCount(1); + mpnsPayload.setTitle("Title"); + mpnsPayload.setType("Type"); + + HashMap customMpnsPayload = new HashMap<>(); + customMpnsPayload.put("a", "a"); + customMpnsPayload.put("b", 1); + customMpnsPayload.put("c", ""); + customMpnsPayload.put("d", null); + mpnsPayload.setCustom(customMpnsPayload); + + pushPayloadHelper.setMpnsPayload(mpnsPayload); + + Map map = pushPayloadHelper.build(); + HashMap pnMpnsMap = (HashMap) map.get("pn_mpns"); + + Assert.assertNotNull(pnMpnsMap); + + Assert.assertEquals(pnMpnsMap.get("back_content"), "Back Content"); + Assert.assertEquals(pnMpnsMap.get("back_title"), "Back Title"); + Assert.assertEquals(pnMpnsMap.get("count"), 1); + Assert.assertEquals(pnMpnsMap.get("title"), "Title"); + Assert.assertEquals(pnMpnsMap.get("type"), "Type"); + Assert.assertEquals(pnMpnsMap.get("a"), "a"); + Assert.assertEquals(pnMpnsMap.get("b"), 1); + Assert.assertEquals(pnMpnsMap.get("c"), ""); + Assert.assertEquals(pnMpnsMap.get("d"), null); + } + + @Test + public void testMicrosoft_Valid() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.MPNSPayload mpnsPayload = new PushPayloadHelper.MPNSPayload(); + + mpnsPayload.setBackContent("Back Content"); + mpnsPayload.setBackTitle("Back Title"); + mpnsPayload.setCount(1); + mpnsPayload.setTitle("Title"); + mpnsPayload.setType("Type"); + + HashMap customMpnsPayload = new HashMap<>(); + customMpnsPayload.put("a", "a"); + customMpnsPayload.put("b", 1); + customMpnsPayload.put("c", ""); + customMpnsPayload.put("d", null); + mpnsPayload.setCustom(customMpnsPayload); + + pushPayloadHelper.setMpnsPayload(mpnsPayload); + + Map map = pushPayloadHelper.build(); + HashMap pnMpnsMap = (HashMap) map.get("pn_mpns"); + + Assert.assertNotNull(pnMpnsMap); + + Assert.assertEquals(pnMpnsMap.get("back_content"), "Back Content"); + Assert.assertEquals(pnMpnsMap.get("back_title"), "Back Title"); + Assert.assertEquals(pnMpnsMap.get("count"), 1); + Assert.assertEquals(pnMpnsMap.get("title"), "Title"); + Assert.assertEquals(pnMpnsMap.get("type"), "Type"); + Assert.assertEquals(pnMpnsMap.get("a"), "a"); + Assert.assertEquals(pnMpnsMap.get("b"), 1); + Assert.assertEquals(pnMpnsMap.get("c"), ""); + Assert.assertEquals(pnMpnsMap.get("d"), null); + } + + @Test + public void testMicrosoft_Empty() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.MPNSPayload mpnsPayload = new PushPayloadHelper.MPNSPayload(); + + pushPayloadHelper.setMpnsPayload(mpnsPayload); + + Map map = pushPayloadHelper.build(); + HashMap pnMpnsMap = (HashMap) map.get("pn_mpns"); + + Assert.assertNull(pnMpnsMap); + } + + @Test + public void testMicrosoft_Custom() { + PushPayloadHelper pushPayloadHelper = new PushPayloadHelper(); + + PushPayloadHelper.MPNSPayload mpnsPayload = new PushPayloadHelper.MPNSPayload(); + + mpnsPayload.setBackContent(""); + mpnsPayload.setBackTitle("Back Title"); + mpnsPayload.setCount(1); + mpnsPayload.setTitle(null); + mpnsPayload.setType("Type"); + + HashMap customMpnsPayload = new HashMap<>(); + customMpnsPayload.put("a", "a"); + customMpnsPayload.put("b", 1); + customMpnsPayload.put("c", ""); + customMpnsPayload.put("d", null); + mpnsPayload.setCustom(customMpnsPayload); + + pushPayloadHelper.setMpnsPayload(mpnsPayload); + + Map map = pushPayloadHelper.build(); + HashMap pnMpnsMap = (HashMap) map.get("pn_mpns"); + + Assert.assertNotNull(pnMpnsMap); + + Assert.assertEquals("", pnMpnsMap.get("back_content")); + Assert.assertEquals("Back Title", pnMpnsMap.get("back_title")); + Assert.assertEquals(1, pnMpnsMap.get("count")); + Assert.assertFalse(pnMpnsMap.containsKey("title")); + Assert.assertEquals("Type", pnMpnsMap.get("type")); + Assert.assertEquals("a", pnMpnsMap.get("a")); + Assert.assertEquals(1, pnMpnsMap.get("b")); + Assert.assertEquals("", pnMpnsMap.get("c")); + Assert.assertFalse(pnMpnsMap.containsKey("d")); + } + +} diff --git a/src/test/java/com/pubnub/api/endpoints/remoteaction/CancellableRemoteAction.java b/src/test/java/com/pubnub/api/endpoints/remoteaction/CancellableRemoteAction.java new file mode 100644 index 000000000..263750237 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/remoteaction/CancellableRemoteAction.java @@ -0,0 +1,34 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.callbacks.PNCallback; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executors; + +interface CancellableRemoteAction extends RemoteAction { + @Override + default T sync() { + return null; + } + + @Override + default void retry() { + + } + + void doAsync(@NotNull PNCallback callback) throws InterruptedException; + + @Override + default void async(@NotNull PNCallback callback) { + //noinspection Convert2Lambda + Executors.newSingleThreadExecutor() + .execute(new Runnable() { + @Override + @SneakyThrows + public void run() { + doAsync(callback); + } + }); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteActionTest.java b/src/test/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteActionTest.java new file mode 100644 index 000000000..3d9741fad --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/remoteaction/ComposableRemoteActionTest.java @@ -0,0 +1,177 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.models.consumer.PNStatus; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.pubnub.api.endpoints.remoteaction.ComposableRemoteAction.firstDo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +public class ComposableRemoteActionTest { + + @Test + public void sync_happyPath() throws PubNubException { + //given + RemoteAction composedAction = firstDo(TestRemoteAction.successful(668)) + .then(integerResult -> TestRemoteAction.successful(integerResult * 2)) + .then(integerResult -> TestRemoteAction.successful(integerResult + 1)); + + //when + int result = composedAction.sync(); + + //then + assertEquals(1337, result); + } + + + @Test + public void async_happyPath() throws InterruptedException { + //given + final CountDownLatch latch = new CountDownLatch(1); + final AtomicInteger result = new AtomicInteger(0); + RemoteAction composedAction = firstDo(TestRemoteAction.successful(668)) + .then(integerResult -> TestRemoteAction.successful(integerResult * 2)) + .then(integerResult -> TestRemoteAction.successful(integerResult + 1)); + + //when + composedAction.async((r, s) -> { + if (r != null) { + result.set(r); + latch.countDown(); + } + }); + + //then + assertTrue(latch.await(1, TimeUnit.SECONDS)); + assertEquals(1337, result.get()); + } + + @Test(expected = PubNubException.class) + public void sync_whenFirstFails_RestIsNotCalled() throws PubNubException { + firstDo(TestRemoteAction.failing()) + .then(integerResult -> { + fail("fail"); + return TestRemoteAction.successful(15); + } + ).sync(); + } + + @Test + public void async_whenFirstFails_RestIsNotCalled() throws InterruptedException { + //given + final CountDownLatch latch = new CountDownLatch(1); + final TestRemoteAction successful = TestRemoteAction.successful(15); + + firstDo(TestRemoteAction.failing()) + .then(integerResult -> successful) + //when + .async((result, status) -> { + latch.countDown(); + }); + + //then + assertTrue(latch.await(1, TimeUnit.SECONDS)); + assertThat(successful.howManyTimesAsyncCalled(), is(0)); + } + + @Test + public void cancel_cancelsCurrentlyRunningTask_RestIsNotCalled() throws InterruptedException { + //given + final CountDownLatch cancelSynchronisingLatch = new CountDownLatch(1); + final CountDownLatch resultSynchronisingLatch = new CountDownLatch(1); + final AtomicBoolean firstAsyncFinished = new AtomicBoolean(false); + final CancellableRemoteAction longRunningTask = new CancellableRemoteAction() { + @Override + public void doAsync(@NotNull PNCallback callback) throws InterruptedException { + cancelSynchronisingLatch.await(); + System.out.println("async"); + callback.onResponse(null, PNStatus.builder().build()); + firstAsyncFinished.set(true); + resultSynchronisingLatch.countDown(); + } + + @Override + public void silentCancel() { + System.out.println("silentCancel"); + cancelSynchronisingLatch.countDown(); + } + }; + final TestRemoteAction successful = TestRemoteAction.successful(15); + RemoteAction composedAction = firstDo(longRunningTask).then(integerResult -> successful); + composedAction.async((r, s) -> { + }); + + //when + composedAction.silentCancel(); + + //then + assertTrue(resultSynchronisingLatch.await(1, TimeUnit.SECONDS)); + assertTrue(firstAsyncFinished.get()); + assertThat(successful.howManyTimesAsyncCalled(), is(0)); + } + + @Test + public void retry_withoutCheckpointStartsFromBeginning() throws InterruptedException { + //given + CountDownLatch countDownLatch = new CountDownLatch(2); + TestRemoteAction firstSuccessful = TestRemoteAction.successful(1); + TestRemoteAction secondSuccessful = TestRemoteAction.successful(1); + TestRemoteAction firstFailing = TestRemoteAction.failingFirstCall(1); + + RemoteAction composedAction = firstDo(firstSuccessful) + .then(integerResult -> secondSuccessful) + .then(integerResult -> firstFailing); + + //when + composedAction.async((r, s) -> { + countDownLatch.countDown(); + if (s.isError()) { + s.retry(); + } + } + ); + + //then + assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS)); + assertThat(firstSuccessful.howManyTimesAsyncCalled(), is(2)); + assertThat(secondSuccessful.howManyTimesAsyncCalled(), is(2)); + assertThat(firstFailing.howManyTimesAsyncCalled(), is(2)); + } + + @Test + public void retry_startsFromCheckpoint() throws InterruptedException { + //given + CountDownLatch countDownLatch = new CountDownLatch(2); + TestRemoteAction firstSuccessful = TestRemoteAction.successful(1); + TestRemoteAction secondSuccessful = TestRemoteAction.successful(1); + TestRemoteAction firstFailing = TestRemoteAction.failingFirstCall(1); + + RemoteAction composedAction = firstDo(firstSuccessful) + .checkpoint() + .then(integerResult -> secondSuccessful) + .then(integerResult -> firstFailing); + + //when + composedAction.async((r, s) -> { + countDownLatch.countDown(); + if (s.isError()) { + s.retry(); + } + } + ); + + //then + assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS)); + assertThat(firstSuccessful.howManyTimesAsyncCalled(), is(1)); + assertThat(secondSuccessful.howManyTimesAsyncCalled(), is(2)); + assertThat(firstFailing.howManyTimesAsyncCalled(), is(2)); + } +} diff --git a/src/test/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteActionTest.java b/src/test/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteActionTest.java new file mode 100644 index 000000000..8db530a0b --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/remoteaction/RetryingRemoteActionTest.java @@ -0,0 +1,180 @@ +package com.pubnub.api.endpoints.remoteaction; + + +import com.pubnub.api.PubNubException; +import com.pubnub.api.enums.PNOperationType; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class RetryingRemoteActionTest { + + Integer expectedValue = 5; + int numberOfRetries = 2; + int timeoutMs = 1000; + ExecutorService executorService = Executors.newSingleThreadExecutor(); + @Test + public void whenSucceedsWrappedActionIsCalledOnce() throws PubNubException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.successful(expectedValue)); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + + //when + Integer result = retryingRemoteAction.sync(); + + //then + Assert.assertEquals(expectedValue, result); + verify(remoteAction, times(1)).sync(); + } + + @Test + public void whenFailingOnceWrappedActionIsCalledTwice() throws PubNubException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.failingFirstCall(expectedValue)); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + + //when + Integer result = retryingRemoteAction.sync(); + + //then + Assert.assertEquals(expectedValue, result); + verify(remoteAction, times(numberOfRetries)).sync(); + } + + @Test + public void whenFailingAlwaysWrappedActionIsCalledTwiceAndThrows() throws PubNubException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.failing()); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + + //when + try { + retryingRemoteAction.sync(); + fail("Exception expected"); + } catch (PubNubException ex) { + //then + verify(remoteAction, times(numberOfRetries)).sync(); + } + } + + @Test + public void whenSucceedsWrappedActionIsCalledOnceAndPassesResult() throws InterruptedException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.successful(expectedValue)); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + CountDownLatch asyncSynchronization = new CountDownLatch(1); + + //when + retryingRemoteAction.async((result, status) -> { + //then + Assert.assertEquals(expectedValue, result); + verify(remoteAction, times(1)).async(any()); + asyncSynchronization.countDown(); + }); + + if (!asyncSynchronization.await(3, TimeUnit.SECONDS)) { + fail("Callback have not been called"); + } + + } + + @Test + public void whenFailingOnceWrappedActionIsCalledTwiceAndPassesResult() throws InterruptedException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.failingFirstCall(expectedValue)); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + CountDownLatch asyncSynchronization = new CountDownLatch(1); + + //when + retryingRemoteAction.async((result, status) -> { + //then + Assert.assertEquals(expectedValue, result); + verify(remoteAction, times(numberOfRetries)).async(any()); + asyncSynchronization.countDown(); + }); + + if (!asyncSynchronization.await(3, TimeUnit.SECONDS)) { + fail("Callback have not been called"); + } + + } + + @Test + public void whenFailingAlwaysWrappedActionIsCalledTwiceAndPassesError() throws InterruptedException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.failing()); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + CountDownLatch asyncSynchronization = new CountDownLatch(1); + + //when + retryingRemoteAction.async((result, status) -> { + //then + Assert.assertTrue(status.isError()); + verify(remoteAction, times(numberOfRetries)).async(any()); + asyncSynchronization.countDown(); + }); + + if (!asyncSynchronization.await(3, TimeUnit.SECONDS)) { + fail("Callback have not been called"); + } + + } + + @Test + public void whenRetryWrappedActionWillBeCalledTwiceTheUsualTime() throws InterruptedException { + //given + TestRemoteAction remoteAction = spy(TestRemoteAction.failing()); + RetryingRemoteAction retryingRemoteAction = RetryingRemoteAction.autoRetry(remoteAction, + numberOfRetries, + PNOperationType.PNFileAction, + executorService); + CountDownLatch asyncSynchronization = new CountDownLatch(2); + + //when + retryingRemoteAction.async((result, status) -> { + //then + if (asyncSynchronization.getCount() == 1) { + Assert.assertTrue(status.isError()); + verify(remoteAction, times(2 * numberOfRetries)).async(any()); + } + asyncSynchronization.countDown(); + if (asyncSynchronization.getCount() == 1) { + status.retry(); + } + }); + + if (!asyncSynchronization.await(3, TimeUnit.SECONDS)) { + fail("Callback have not been called"); + } + + } + +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/endpoints/remoteaction/TestRemoteAction.java b/src/test/java/com/pubnub/api/endpoints/remoteaction/TestRemoteAction.java new file mode 100644 index 000000000..c94a4c38b --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/remoteaction/TestRemoteAction.java @@ -0,0 +1,93 @@ +package com.pubnub.api.endpoints.remoteaction; + +import com.pubnub.api.PubNubError; +import com.pubnub.api.PubNubException; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.models.consumer.PNStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class TestRemoteAction implements RemoteAction { + + private final Output output; + private final FailingStrategy failingStrategy; + private final Executor executor = Executors.newSingleThreadExecutor(); + private final AtomicInteger asyncCallmeter = new AtomicInteger(0); + private final AtomicInteger callsToFail; + private PNCallback callback; + + TestRemoteAction(Output output, FailingStrategy failingStrategy) { + this.output = output; + this.failingStrategy = failingStrategy; + this.callsToFail = new AtomicInteger(failingStrategy.numberOfCalls); + } + + public static TestRemoteAction failing() { + return new TestRemoteAction<>(null, FailingStrategy.ALWAYS_FAIL); + } + + public static TestRemoteAction failingFirstCall(T output) { + return new TestRemoteAction<>(output, FailingStrategy.FAIL_FIRST_CALLS); + } + + public static TestRemoteAction successful(T output) { + return new TestRemoteAction<>(output, FailingStrategy.NEVER_FAIL); + } + + @Override + public Output sync() throws PubNubException { + if (failingStrategy == FailingStrategy.ALWAYS_FAIL) { + throw PubNubException.builder().pubnubError(PubNubError.builder().errorCode(500).build()).build(); + } else if (failingStrategy == FailingStrategy.FAIL_FIRST_CALLS && this.callsToFail.getAndDecrement() > 0) { + throw PubNubException.builder().pubnubError(PubNubError.builder().errorCode(500).build()).build(); + } else { + return output; + } + } + + @Override + public void async(@NotNull PNCallback callback) { + this.callback = callback; + asyncCallmeter.incrementAndGet(); + executor.execute(() -> { + if (failingStrategy == FailingStrategy.ALWAYS_FAIL) { + callback.onResponse(null, PNStatus.builder().error(true).build()); + } else if (failingStrategy == FailingStrategy.FAIL_FIRST_CALLS && this.callsToFail.getAndDecrement() > 0) { + callback.onResponse(null, PNStatus.builder().error(true).build()); + } else { + callback.onResponse(output, PNStatus.builder().build()); + } + }); + } + + @Override + public void retry() { + if (callback != null) { + async(this.callback); + } + } + + @Override + public void silentCancel() { + + } + + public int howManyTimesAsyncCalled() { + return asyncCallmeter.get(); + } + + enum FailingStrategy { + NEVER_FAIL(0), + ALWAYS_FAIL(0), + FAIL_FIRST_CALLS(1); + + int numberOfCalls; + + FailingStrategy(int numberOfCalls) { + this.numberOfCalls = numberOfCalls; + } + } +} diff --git a/src/test/java/com/pubnub/api/managers/BasePathManagerTest.java b/src/test/java/com/pubnub/api/managers/BasePathManagerTest.java new file mode 100644 index 000000000..7ceddf4d6 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/BasePathManagerTest.java @@ -0,0 +1,122 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class BasePathManagerTest { + + private PNConfiguration pnConfiguration; + + @Before + public void beforeEach() throws IOException { + pnConfiguration = new PNConfiguration(); + } + + @Test + public void stdOriginNotSecure() { + pnConfiguration.setSecure(false); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("http://ps.pndsn.com", basePathManager.getBasePath()); + } + + @Test + public void stdOriginSecure() { + pnConfiguration.setSecure(true); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("https://ps.pndsn.com", basePathManager.getBasePath()); + } + + @Test + public void customOriginNotSecure() { + pnConfiguration.setOrigin("custom.origin.com"); + pnConfiguration.setSecure(false); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("http://custom.origin.com", basePathManager.getBasePath()); + } + + @Test + public void customOriginSecure() { + pnConfiguration.setOrigin("custom.origin.com"); + pnConfiguration.setSecure(true); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("https://custom.origin.com", basePathManager.getBasePath()); + } + + @Test + public void customOriginNotSecureWithCacheBusting() { + pnConfiguration.setOrigin("custom.origin.com"); + pnConfiguration.setCacheBusting(true); + pnConfiguration.setSecure(false); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("http://custom.origin.com", basePathManager.getBasePath()); + } + + @Test + public void customOriginSecureWithCacheBusting() { + pnConfiguration.setOrigin("custom.origin.com"); + pnConfiguration.setSecure(true); + pnConfiguration.setCacheBusting(true); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("https://custom.origin.com", basePathManager.getBasePath()); + } + + @Test + public void cacheBustingNotSecure() { + pnConfiguration.setCacheBusting(true); + pnConfiguration.setSecure(false); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("http://ps1.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps2.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps3.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps4.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps5.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps6.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps7.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps8.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps9.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps10.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps11.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps12.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps13.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps14.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps15.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps16.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps17.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps18.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps19.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps20.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("http://ps1.pndsn.com", basePathManager.getBasePath()); + } + + @Test + public void cacheBustingSecure() { + pnConfiguration.setCacheBusting(true); + BasePathManager basePathManager = new BasePathManager(pnConfiguration); + Assert.assertEquals("https://ps1.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps2.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps3.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps4.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps5.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps6.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps7.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps8.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps9.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps10.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps11.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps12.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps13.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps14.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps15.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps16.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps17.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps18.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps19.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps20.pndsn.com", basePathManager.getBasePath()); + Assert.assertEquals("https://ps1.pndsn.com", basePathManager.getBasePath()); + } + +} diff --git a/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java b/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java new file mode 100644 index 000000000..65cd2fb07 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/FastSubscriptionManagerTest.java @@ -0,0 +1,247 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNub; +import com.pubnub.api.builder.dto.ChangeTemporaryUnavailableOperation; +import com.pubnub.api.builder.dto.PubSubOperation; +import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.managers.subscription.utils.RequestDetails; +import com.pubnub.api.managers.subscription.utils.ResponseHolder; +import com.pubnub.api.managers.subscription.utils.ResponseSupplier; +import com.pubnub.api.managers.token_manager.TokenManager; +import com.pubnub.api.models.server.SubscribeEnvelope; +import com.pubnub.api.models.server.SubscribeMetadata; +import com.pubnub.api.services.SubscribeService; +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import org.awaitility.core.ThrowingRunnable; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import retrofit2.Response; + +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.Random; + +import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.pubnub; +import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.retrofitManagerMock; +import static com.pubnub.api.managers.subscription.utils.SubscriptionTestUtils.telemetryManager; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class FastSubscriptionManagerTest { + + public static final String FAKE_REGION = "12"; + private final ListenerManager listenerManagerMock = mock(ListenerManager.class); + private final ReconnectionManager reconnectionManagerMock = mock(ReconnectionManager.class); + private final DelayedReconnectionManager delayedReconnectionManagerMock = mock(DelayedReconnectionManager.class); + + @Test + public void performsLongPollingAfterTimeout() throws IllegalAccessException { + final ResponseSupplier responseSupplier = requestDetails -> new ResponseHolder<>(new SocketTimeoutException( + "timeout")); + + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + + final SubscriptionManager subscriptionManager = spy(subscriptionManagerUnderTest(retrofitManagerMock)); + + final SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(singletonList("ch1")) + .channelGroups(emptyList()) + .build(); + + subscriptionManager.adaptSubscribeBuilder(subscribeOperation); + + await().atMost(2, SECONDS).untilAsserted(() -> { + verify(subscriptionManager, atLeast(2)).startSubscribeLoop(any()); + }); + } + + @Test + public void disconnectsAndSchedulesReconnectionOnUnknownHostException() throws IllegalAccessException { + final ResponseSupplier responseSupplier = requestDetails -> new ResponseHolder<>(new UnknownHostException( + "example.com")); + + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + + final SubscriptionManager subscriptionManager = spy(subscriptionManagerUnderTest(retrofitManagerMock)); + + final SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(singletonList("ch1")) + .channelGroups(emptyList()) + .build(); + + subscriptionManager.adaptSubscribeBuilder(subscribeOperation); + + await().atMost(2, SECONDS).untilAsserted(() -> { + verify(subscriptionManager, times(1)).disconnect(); + verify(reconnectionManagerMock, times(1)).startPolling(); + }); + } + + @Test + public void forbiddenChannelsAddedToTemporaryUnavailable() throws IllegalAccessException, InterruptedException { + final String channel = "ch1"; + final String rawResponseBody = String.format( + "{\"message\":\"Forbidden\",\"payload\":{\"channels\":[\"%s\"]},\"error\":true,\"service\":\"Access Manager\",\"status\":403}", + channel); + + final ResponseSupplier responseSupplier = requestDetails -> { + final ResponseBody responseBody = ResponseBody.create(MediaType.parse("json"), rawResponseBody); + return new ResponseHolder<>(Response.error(403, responseBody)); + }; + + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + + final SubscriptionManager subscriptionManager = spy(subscriptionManagerUnderTest(retrofitManagerMock)); + + subscriptionManager.subscriptionState.handleOperation(subscribeOperation(channel)); + + subscriptionManager.reconnect(); + + final int connectTimeout = subscriptionManager.pubnub.getConfiguration().getConnectTimeout(); + + await().atMost(connectTimeout, SECONDS).untilAsserted(() -> + assertEquals(emptyList(), + subscriptionManager.subscriptionState.subscriptionStateData(false, + StateManager.ChannelFilter.WITHOUT_TEMPORARY_UNAVAILABLE).getChannels())); + } + + @Test + public void temporaryUnavailableAttemptedToSubscribeAfterSomeTime() { + final String channel = "ch1"; + final ResponseSupplier responseSupplier = requestDetails -> { + final SubscribeMetadata subscribeMetadata = new SubscribeMetadata(System.currentTimeMillis(), + FAKE_REGION); + final SubscribeEnvelope subscribeEnvelope = new SubscribeEnvelope(emptyList(), + subscribeMetadata); + return new ResponseHolder<>(Response.success(subscribeEnvelope)); + }; + + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + + final SubscriptionManager subscriptionManager = spy(subscriptionManagerUnderTest(retrofitManagerMock)); + + subscriptionManager.subscriptionState.handleOperation(subscribeOperation(channel), + unavailableOperation(channel)); + final int connectTimeout = subscriptionManager.pubnub.getConfiguration().getConnectTimeout(); + assertEquals(emptyList(), + subscriptionManager.subscriptionState.subscriptionStateData(false, + StateManager.ChannelFilter.WITHOUT_TEMPORARY_UNAVAILABLE).getChannels()); + + subscriptionManager.reconnect(); + + await().atMost(3 * connectTimeout, SECONDS).untilAsserted(() -> + assertEquals(singletonList(channel), + subscriptionManager.subscriptionState.subscriptionStateData(false, + StateManager.ChannelFilter.WITHOUT_TEMPORARY_UNAVAILABLE).getChannels())); + } + + private PubSubOperation subscribeOperation(String channel) { + return SubscribeOperation.builder().channels(singletonList(channel)).build(); + } + + private PubSubOperation unavailableOperation(String channel) { + return ChangeTemporaryUnavailableOperation.builder().unavailableChannel(channel).build(); + } + + private PubSubOperation availableOperation(String channel) { + return ChangeTemporaryUnavailableOperation.builder().availableChannel(channel).build(); + } + + @Test + public void noSubscribeOnUnchangedState() { + long timeToken = System.currentTimeMillis(); + final ResponseSupplier responseSupplier = requestDetails -> { + final SubscribeEnvelope subscribeEnvelope = new SubscribeEnvelope(emptyList(), + new SubscribeMetadata(timeToken, FAKE_REGION)); + try { + SECONDS.sleep(5); + } catch (InterruptedException e) { + } + return new ResponseHolder<>(Response.success(200, subscribeEnvelope)); + }; + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + final SubscribeService spiedSubscribeService = retrofitManagerMock.getSubscribeService(); + final SubscriptionManager subscriptionManager = subscriptionManagerUnderTest(retrofitManagerMock); + + final SubscribeOperation subscribeOperation = SubscribeOperation.builder() + .channels(singletonList("ch1")) + .channelGroups(singletonList("group1")) + .build(); + + for (int i = 0; i < new Random().nextInt(10) + 1; i++) { + subscriptionManager.adaptSubscribeBuilder(subscribeOperation); + } + + await().atMost(1, SECONDS).untilAsserted(() -> + verify(spiedSubscribeService, times(1)).subscribe(any(), any(), any()) + ); + } + + @Test + public void subscribeOnChangedState() { + long timeToken = System.currentTimeMillis(); + final ResponseSupplier responseSupplier = new ResponseSupplier() { + @Override + public ResponseHolder get(final RequestDetails requestDetails) { + final SubscribeEnvelope subscribeEnvelope = new SubscribeEnvelope(emptyList(), + new SubscribeMetadata(timeToken, FAKE_REGION)); + try { + SECONDS.sleep(5); + } catch (InterruptedException e) { + } + return new ResponseHolder<>(Response.success(200, subscribeEnvelope)); + } + }; + final RetrofitManager retrofitManagerMock = retrofitManagerMock(responseSupplier); + final SubscribeService spiedSubscribeService = retrofitManagerMock.getSubscribeService(); + final SubscriptionManager subscriptionManager = subscriptionManagerUnderTest(retrofitManagerMock); + + final SubscribeOperation subscribeOperation1 = SubscribeOperation.builder() + .channels(singletonList("ch1")) + .channelGroups(singletonList("group1")) + .build(); + + final SubscribeOperation subscribeOperation2 = SubscribeOperation.builder() + .channels(singletonList("ch2")) + .channelGroups(singletonList("group2")) + .build(); + + subscriptionManager.adaptSubscribeBuilder(subscribeOperation1); + subscriptionManager.adaptSubscribeBuilder(subscribeOperation2); + + await().atMost(1, SECONDS).untilAsserted(new ThrowingRunnable() { + @Override + public void run() throws Throwable { + verify(spiedSubscribeService, times(2)).subscribe(any(), any(), any()); + } + }); + } + + @NotNull + private SubscriptionManager subscriptionManagerUnderTest(final RetrofitManager retrofitManagerMock) { + final PubNub pubnub = spy(pubnub(retrofitManagerMock)); + final TelemetryManager telemetryManager = spy(telemetryManager(pubnub)); + final StateManager stateManager = spy(new StateManager(pubnub.getConfiguration())); + final DuplicationManager duplicationManager = spy(new DuplicationManager(pubnub.getConfiguration())); + + return new SubscriptionManager(pubnub, + retrofitManagerMock, + telemetryManager, + stateManager, + listenerManagerMock, + reconnectionManagerMock, + delayedReconnectionManagerMock, + duplicationManager, + new TokenManager()); + } +} diff --git a/src/test/java/com/pubnub/api/managers/PublishSequenceManagerTest.java b/src/test/java/com/pubnub/api/managers/PublishSequenceManagerTest.java new file mode 100644 index 000000000..2ab72802e --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/PublishSequenceManagerTest.java @@ -0,0 +1,18 @@ +package com.pubnub.api.managers; + +import org.junit.Assert; +import org.junit.Test; + +public class PublishSequenceManagerTest { + + @Test + public void testSequenceManager() { + PublishSequenceManager publishSequenceManager = new PublishSequenceManager(2); + + Assert.assertEquals(1, publishSequenceManager.getNextSequence()); + Assert.assertEquals(2, publishSequenceManager.getNextSequence()); + Assert.assertEquals(1, publishSequenceManager.getNextSequence()); + Assert.assertEquals(2, publishSequenceManager.getNextSequence()); + } +} + diff --git a/src/test/java/com/pubnub/api/managers/ReconnectionManagerTest.java b/src/test/java/com/pubnub/api/managers/ReconnectionManagerTest.java new file mode 100644 index 000000000..4e45305be --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/ReconnectionManagerTest.java @@ -0,0 +1,39 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.enums.PNReconnectionPolicy; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ReconnectionManagerTest { + @Test + public void reconnectionIntervalsEqualsForLinear() { + PNConfiguration pnConfiguration = new PNConfiguration(); + PubNub pubNub = new PubNub(pnConfiguration); + pnConfiguration.setReconnectionPolicy(PNReconnectionPolicy.LINEAR); + final ReconnectionManager reconnectionManagerUnderTest = new ReconnectionManager(pubNub); + + int firstInterval = reconnectionManagerUnderTest.getNextInterval(); + int secondInterval = reconnectionManagerUnderTest.getNextInterval(); + + assertEquals(secondInterval, firstInterval); + } + + @Test + public void reconnectionIntervalsIncreaseForExponential() { + PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setReconnectionPolicy(PNReconnectionPolicy.EXPONENTIAL); + PubNub pubNub = new PubNub(pnConfiguration); + final ReconnectionManager reconnectionManagerUnderTest = new ReconnectionManager(pubNub); + + int firstInterval = reconnectionManagerUnderTest.getNextInterval(); + int secondInterval = reconnectionManagerUnderTest.getNextInterval(); + int thirdInterval = reconnectionManagerUnderTest.getNextInterval(); + + assertTrue(firstInterval < secondInterval); + assertTrue(secondInterval < thirdInterval); + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/managers/StateManagerTest.java b/src/test/java/com/pubnub/api/managers/StateManagerTest.java new file mode 100644 index 000000000..ada9a7b8e --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/StateManagerTest.java @@ -0,0 +1,214 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.builder.dto.PresenceOperation; +import com.pubnub.api.builder.dto.PubSubOperation; +import com.pubnub.api.builder.dto.StateOperation; +import com.pubnub.api.builder.dto.SubscribeOperation; +import com.pubnub.api.managers.StateManager.SubscriptionStateData; +import org.hamcrest.Matchers; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.managers.StateManager.HeartbeatStateData; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class StateManagerTest { + final private List channelsToSubscribe = asList("sub1", "sub2"); + final private List channelsToTracePresence = asList("pres1", "pres2"); + final private String state = "state"; + + @Test + public void heartbeatSendsAllChannelsWhenManualModeTurnedOff() { + //given + final PNConfiguration pnConfiguration = new PNConfiguration(); + final StateManager stateManagerUnderTest = new StateManager(pnConfiguration); + + //when + stateManagerUnderTest.handleOperation(subscribeOperation(channelsToSubscribe), + presenceOperation(channelsToTracePresence)); + + final HeartbeatStateData heartbeatStateData = stateManagerUnderTest.heartbeatStateData(); + final SubscriptionStateData subscriptionStateData = stateManagerUnderTest.subscriptionStateData(true); + + //then + assertThat( + subscriptionStateData.getChannels(), + hasItems(channelsToSubscribe.toArray(new String[]{})) + ); + + assertThat( + heartbeatStateData.getHeartbeatChannels(), + both(hasItems(channelsToTracePresence.toArray(new String[]{}))) + .and(hasItems(channelsToSubscribe.toArray(new String[]{}))) + ); + } + + @Test + public void heartbeatSendsOnlyPresenceChannelsWhenManualModeTurnedOn() { + //given + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setManagePresenceListManually(true); + final StateManager stateManagerUnderTest = new StateManager(pnConfiguration); + + //when + stateManagerUnderTest.handleOperation(subscribeOperation(channelsToSubscribe), + presenceOperation(channelsToTracePresence)); + + final HeartbeatStateData heartbeatStateData = stateManagerUnderTest + .heartbeatStateData(); + final SubscriptionStateData subscriptionStateData = stateManagerUnderTest + .subscriptionStateData(true); + + //then + assertThat( + subscriptionStateData.getChannels(), + hasItems(channelsToSubscribe.toArray(new String[]{})) + ); + + assertThat( + heartbeatStateData.getHeartbeatChannels(), + both(hasItems(channelsToTracePresence.toArray(new String[]{}))) + .and(not(hasItems(channelsToSubscribe.toArray(new String[]{})))) + ); + } + + @Test + public void whenManualModeStateOperationAddStateToSubscribedChannels() { + //given + StateManager stateManagerUnderTest = new StateManager(withManualPresenceMode(config())); + + //when + stateManagerUnderTest.handleOperation(subscribeOperation(channelsToSubscribe), + stateOperation(channelsToSubscribe, state)); + + //then + assertThat(stateManagerUnderTest.subscriptionStateData(false).getStatePayload(), + Matchers.equalTo(mapChannelsToState(channelsToSubscribe, state))); + assertThat(stateManagerUnderTest.heartbeatStateData().getStatePayload(), + Matchers.equalTo(emptyMap())); + + } + + @Test + public void whenManualModeStateOperationAddStateToHeartbeatChannels() { + //given + StateManager stateManagerUnderTest = new StateManager(withManualPresenceMode(config())); + + //when + stateManagerUnderTest.handleOperation(presenceOperation(channelsToTracePresence), + stateOperation(channelsToTracePresence, state)); + + //then + assertThat(stateManagerUnderTest.subscriptionStateData(false).getStatePayload(), + Matchers.equalTo(emptyMap())); + assertThat(stateManagerUnderTest.heartbeatStateData().getStatePayload(), + Matchers.equalTo(mapChannelsToState(channelsToTracePresence, state))); + + } + + @Test + public void whenManualModeOffStateOperationDoNotAddStateToHeartbeatChannels() { + //given + StateManager stateManagerUnderTest = new StateManager(withoutManualPresenceMode(config())); + + //when + stateManagerUnderTest.handleOperation(presenceOperation(channelsToTracePresence), + stateOperation(channelsToTracePresence, state)); + + //then + assertThat(stateManagerUnderTest.subscriptionStateData(false).getStatePayload(), + Matchers.equalTo(emptyMap())); + assertThat(stateManagerUnderTest.heartbeatStateData().getStatePayload(), + Matchers.equalTo(emptyMap())); + } + + @Test + public void whenManualModeOffStateOperationAddStateToSubscribedChannels() { + //given + StateManager stateManagerUnderTest = new StateManager(withoutManualPresenceMode(config())); + + //when + stateManagerUnderTest.handleOperation(subscribeOperation(channelsToSubscribe), + stateOperation(channelsToSubscribe, state)); + + //then + assertThat(stateManagerUnderTest.subscriptionStateData(false).getStatePayload(), + Matchers.equalTo(mapChannelsToState(channelsToSubscribe, state))); + assertThat(stateManagerUnderTest.heartbeatStateData().getStatePayload(), + Matchers.equalTo(emptyMap())); + + } + + @Test + public void whenManualModeStateOperationAddStateToSubscribedAndHeartbeatIfBothPresent() { + //given + StateManager stateManagerUnderTest = new StateManager(withManualPresenceMode(config())); + + //when + stateManagerUnderTest.handleOperation(subscribeOperation(channelsToSubscribe), + presenceOperation(channelsToSubscribe), + stateOperation(channelsToSubscribe, state)); + + //then + assertThat(stateManagerUnderTest.subscriptionStateData(false).getStatePayload(), + Matchers.equalTo(mapChannelsToState(channelsToSubscribe, state))); + assertThat(stateManagerUnderTest.heartbeatStateData().getStatePayload(), + Matchers.equalTo(mapChannelsToState(channelsToSubscribe, state))); + } + + private Map mapChannelsToState(List channels, Object state) { + HashMap result = new HashMap<>(); + + for(String channel : channels) { + result.put(channel, state); + } + + return result; + } + + private PNConfiguration config() { + return new PNConfiguration(); + } + + private PNConfiguration withManualPresenceMode(PNConfiguration config) { + //noinspection deprecation + config.setManagePresenceListManually(true); + return config; + } + + private PNConfiguration withoutManualPresenceMode(PNConfiguration config) { + //noinspection deprecation + config.setManagePresenceListManually(false); + return config; + } + + private PresenceOperation presenceOperation(final List channelsToTracePresence) { + return PresenceOperation.builder() + .channels(channelsToTracePresence) + .connected(true) + .build(); + } + + private SubscribeOperation subscribeOperation(final List channelsToSubscribe) { + return SubscribeOperation.builder() + .channels(channelsToSubscribe) + .build(); + } + + private PubSubOperation stateOperation(final List channelsToSubscribe, Object state) { + return StateOperation + .builder() + .channels(channelsToSubscribe) + .state(state) + .build(); + } +} diff --git a/src/test/java/com/pubnub/api/managers/SubscriptionManagerTest.java b/src/test/java/com/pubnub/api/managers/SubscriptionManagerTest.java new file mode 100644 index 000000000..0cc2069e9 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/SubscriptionManagerTest.java @@ -0,0 +1,3132 @@ +package com.pubnub.api.managers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.http.QueryParameter; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.callbacks.PNCallback; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.enums.PNHeartbeatNotificationOptions; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.enums.PNStatusCategory; +import com.pubnub.api.models.consumer.PNStatus; +import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadataResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; +import com.pubnub.api.models.consumer.objects_api.uuid.PNUUIDMetadataResult; +import com.pubnub.api.models.consumer.presence.PNSetStateResult; +import com.pubnub.api.models.consumer.pubsub.PNMessageResult; +import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult; +import com.pubnub.api.models.consumer.pubsub.PNSignalResult; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.consumer.pubsub.message_actions.PNMessageActionResult; +import org.awaitility.Awaitility; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.CheckedOutputStream; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.findAll; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + + +public class SubscriptionManagerTest extends TestHarness { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(options().port(PORT), false); + + private PubNub pubnub; + + @Before + public void beforeEach() throws IOException { + pubnub = this.createPubNubInstance(); + wireMockRule.start(); + } + + @After + public void afterEach() { + pubnub.destroy(); + pubnub = null; + wireMockRule.stop(); + } + + @Test + public void testGetSubscribedChannels() { + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + List channels = pubnub.getSubscribedChannels(); + + assertTrue(channels.contains("ch1")); + assertTrue(channels.contains("ch2")); + } + + @Test + public void testGetSubscribedEmptyChannel() { + + final AtomicInteger gotMessages = new AtomicInteger(); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.subscribe().channels(Arrays.asList("")).execute(); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + gotMessages.addAndGet(1); + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + gotMessages.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + gotMessages.addAndGet(1); + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessages, org.hamcrest.core.IsEqual.equalTo(0)); + + } + + @Test + public void testGetSubscribedEmptyChannelGroup() { + + final AtomicInteger gotMessages = new AtomicInteger(); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.subscribe().channelGroups(Arrays.asList("")).execute(); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + gotMessages.addAndGet(1); + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + gotMessages.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + gotMessages.addAndGet(1); + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessages, org.hamcrest.core.IsEqual.equalTo(0)); + + } + + @Test + public void testGetSubscribedChannelGroups() { + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.subscribe().channelGroups(Arrays.asList("cg1", "cg2")).execute(); + + List groups = pubnub.getSubscribedChannelGroups(); + + assertTrue(groups.contains("cg1")); + assertTrue(groups.contains("cg2")); + } + + @Test + public void testPubNubUnsubscribeAll() { + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")) + .channelGroups(Arrays.asList("cg1", "cg2")) + .withPresence() + .execute(); + + List channels = pubnub.getSubscribedChannels(); + assertTrue(channels.contains("ch1")); + assertTrue(channels.contains("ch2")); + + List groups = pubnub.getSubscribedChannelGroups(); + assertTrue(groups.contains("cg1")); + assertTrue(groups.contains("cg2")); + + pubnub.unsubscribeAll(); + + channels = pubnub.getSubscribedChannels(); + assertEquals(0, channels.size()); + + groups = pubnub.getSubscribedChannelGroups(); + assertEquals(0, groups.size()); + } + + @Test + public void testSubscribeBuilder() { + final AtomicInteger gotStatus = new AtomicInteger(); + final AtomicBoolean gotMessage = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1); + } + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() > 0); + assertEquals("Message", pubnub.getMapper().elementToString(message.getMessage(), "text")); + assertEquals("coolChannel", message.getChannel()); + assertEquals(null, message.getSubscription()); + assertEquals("Publisher-A", message.getPublisher()); + gotMessage.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessage, org.hamcrest.core.IsEqual.equalTo(true)); + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testSubscribeDuplicateDisabledBuilder() { + final AtomicInteger gotMessages = new AtomicInteger(); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"},{\"a\":\"4\",\"f\":0," + + "\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + gotMessages.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessages, org.hamcrest.core.IsEqual.equalTo(2)); + } + + @Test + public void testSubscribeDuplicateBuilder() { + this.pubnub.getConfiguration().setDedupOnSubscribe(true); + final AtomicInteger gotMessages = new AtomicInteger(); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"},{\"a\":\"4\",\"f\":0," + + "\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + gotMessages.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessages, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testSubscribeDuplicateWithLimitBuilder() { + this.pubnub.getConfiguration().setDedupOnSubscribe(true); + this.pubnub.getConfiguration().setMaximumMessagesCacheSize(1); + + final AtomicInteger gotMessages = new AtomicInteger(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message1\"},\"b\":\"coolChannel\"},{\"a\":\"4\",\"f\":0," + + "\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message2\"},\"b\":\"coolChannel\"},{\"a\":\"4\",\"f\":0," + + "\"i\":\"Publisher-A\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message1\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + gotMessages.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(3, TimeUnit.SECONDS).untilAtomic(gotMessages, org.hamcrest.core.IsEqual.equalTo(3)); + } + + @Test + public void testQueueNotificationsBuilderNoThresholdSpecified() { + pubnub.getConfiguration().setRequestMessageCountThreshold(null); + final AtomicBoolean gotStatus = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getCategory() == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(false)); + } + + @Test + public void testQueueNotificationsBuilderBelowThreshold() { + pubnub.getConfiguration().setRequestMessageCountThreshold(10); + final AtomicBoolean gotStatus = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getCategory() == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(false)); + } + + @Test + public void testQueueNotificationsBuilderThresholdMatched() { + pubnub.getConfiguration().setRequestMessageCountThreshold(1); + final AtomicBoolean gotStatus = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"o\":{\"t\":\"14737141991877032\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getCategory() == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void testQueueNotificationsBuilderThresholdExceeded() { + pubnub.getConfiguration().setRequestMessageCountThreshold(1); + final AtomicBoolean gotStatus = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"m\":[{\"a\":\"4\",\"b\":\"coolChannel\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"f\":0,\"i\":\"Client-g5d4g\"," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"o\":{\"r\":2," + + "\"t\":\"14737141991877032\"},\"p\":{\"r\":1,\"t\":\"14607577960925503\"}},{\"a\":\"5\"," + + "\"b\":\"coolChannel2\",\"c\":\"coolChannel2\",\"d\":{\"text\":\"Message2\"},\"f\":0," + + "\"i\":\"Client-g5d4g\",\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4g\",\"o\":{\"r\":2," + + "\"t\":\"14737141991877033\"},\"p\":{\"r\":1,\"t\":\"14607577960925504\"}}],\"t\":{\"r\":1," + + "\"t\":\"14607577960932487\"}}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getCategory() == PNStatusCategory.PNRequestMessageCountExceededCategory) { + gotStatus.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void testSubscribeBuilderWithAccessManager403Error() { + final AtomicInteger gotStatus = new AtomicInteger(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withStatus(403).withBody("{\"message\":\"Forbidden\"," + + "\"payload\":{\"channels\":[\"ch1\", \"ch2\"], \"channel-groups\":[\":cg1\", \":cg2\"]}," + + "\"error\":true,\"service\":\"Access Manager\",\"status\":403}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNAccessDeniedCategory) { + + assert status.getAffectedChannels() != null; + + assertEquals(PNStatusCategory.PNAccessDeniedCategory, status.getCategory()); + assertEquals(Arrays.asList("ch1", "ch2"), status.getAffectedChannels()); + assertEquals(Arrays.asList("cg1", "cg2"), status.getAffectedChannelGroups()); + gotStatus.addAndGet(1); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(1)); + } + + @Test + public void testNamingSubscribeChannelGroupBuilder() { + final AtomicBoolean gotStatus = new AtomicBoolean(false); + final AtomicBoolean gotMessage = new AtomicBoolean(false); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChannelGroup\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + assert status.getAffectedChannels() != null; + assertEquals(2, status.getAffectedChannels().size()); + gotStatus.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() > 0); + assertEquals("Message", pubnub.getMapper().elementToString(message.getMessage(), "text")); + assertEquals("coolChannel", message.getChannel()); + assertEquals("coolChannelGroup", message.getSubscription()); + gotMessage.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(gotMessage); + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(gotStatus); + + } + + @Test + public void testPresenceSubscribeBuilder() { + final AtomicInteger gotStatus = new AtomicInteger(); + final AtomicBoolean gotMessage = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14614512228786519\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14614512228418349\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel-pnpres\"," + + "\"d\":{\"action\": \"join\", \"timestamp\": 1461451222, \"uuid\": " + + "\"4a6d5df7-e301-4e73-a7b7-6af9ab484eb0\", \"occupancy\": 1},\"b\":\"coolChannel-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1); + } + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + assertEquals("coolChannel", presence.getChannel()); + assertEquals(null, presence.getSubscription()); + gotMessage.set(true); + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotMessage, org.hamcrest.core.IsEqual.equalTo(true)); + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testPresenceChannelGroupSubscribeBuilder() { + final AtomicInteger gotStatus = new AtomicInteger(); + final AtomicBoolean gotMessage = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14614512228786519\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14614512228418349\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel-pnpres\"," + + "\"d\":{\"action\": \"join\", \"timestamp\": 1461451222, \"uuid\": " + + "\"4a6d5df7-e301-4e73-a7b7-6af9ab484eb0\", \"occupancy\": 1}," + + "\"b\":\"coolChannelGroup-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + gotStatus.addAndGet(1); + } + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + assertEquals("coolChannel", presence.getChannel()); + assertEquals("coolChannelGroup", presence.getSubscription()); + gotMessage.set(true); + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotMessage, org.hamcrest.core.IsEqual.equalTo(true)); + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotStatus, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + + @Test + public void testSubscribeSlidingBuilder() { + final AtomicBoolean gotMessage1 = new AtomicBoolean(); + final AtomicBoolean gotMessage2 = new AtomicBoolean(); + final AtomicBoolean gotMessage3 = new AtomicBoolean(); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"3\",\"r\":1},\"m\":[{\"a\":\"4\",\"f\":0," + + "\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("3")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"10\",\"r\":1},\"m\":[{\"a\":\"4\",\"f\":0," + + "\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message3\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("tt", matching("10")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"20\",\"r\":1},\"m\":[{\"a\":\"4\",\"f\":0," + + "\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Message10\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + + if (message.getMessage().getAsJsonObject().get("text").getAsString().equals("Message")) { + gotMessage1.set(true); + } else if (message.getMessage().getAsJsonObject().get("text").getAsString().equals("Message3")) { + gotMessage2.set(true); + } else if (message.getMessage().getAsJsonObject().get("text").getAsString().equals("Message10")) { + gotMessage3.set(true); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotMessage1, + org.hamcrest.core.IsEqual.equalTo(true)); + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotMessage2, + org.hamcrest.core.IsEqual.equalTo(true)); + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(gotMessage3, + org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void testSubscribeBuilderNumber() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\": 10," + + "\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + assertEquals(10, message.getMessage().getAsInt()); + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribeBuilderWithMetadata() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody(" {\"t\":{\"t\":\"14858178301085322\",\"r\":7},\"m\":[{\"a\":\"4\"," + + "\"f\":512,\"i\":\"02a7b822-220c-49b0-90c4-d9cbecc0fd85\",\"s\":1," + + "\"p\":{\"t\":\"14858178301075219\",\"r\":7},\"k\":\"demo-36\",\"c\":\"chTest\"," + + "\"u\":{\"status_update\":{\"lat\":55.752023906250656,\"lon\":37.61749036080494," + + "\"driver_id\":4722}},\"d\":{\"City\":\"Goiania\",\"Name\":\"Marcelo\"}}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + assertEquals("{\"status_update\":{\"lat\":55.752023906250656,\"lon\":37.61749036080494," + + "\"driver_id\":4722}}", message.getUserMetadata().toString()); + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribeBuilderWithState() throws PubNubException { + final String expectedPayload = PubNubUtil.urlDecode("%7B%22ch1%22%3A%5B%22p1%22%2C%22p2%22%5D%2C%22cg2%22%3A" + + "%5B%22p1%22%2C%22p2%22%5D%7D"); + final Map expectedMap = pubnub.getMapper().fromJson(expectedPayload, Map.class); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.getConfiguration().setPresenceTimeout(20); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).channelGroups(Arrays.asList("cg1", "cg2")).execute(); + pubnub.setPresenceState().channels(Arrays.asList("ch1")).channelGroups(Arrays.asList("cg2")) + .state(Arrays.asList("p1", "p2")) + .async((result, status) -> { + }); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .until(() -> findAll(getRequestedFor(urlMatching( + "/v2/subscribe/" + pubnub.getConfiguration().getSubscribeKey() + "/ch2,ch1/.*"))).stream().anyMatch(req -> { + String stateString = PubNubUtil.urlDecode(req.queryParameter("state").firstValue()); + Map actualMap = null; + try { + actualMap = pubnub.getMapper().fromJson(stateString, Map.class); + } catch (PubNubException e) { + e.printStackTrace(); + } + return actualMap != null && actualMap.equals(expectedMap); + })); + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .until(() -> findAll(getRequestedFor(urlMatching( + "/v2/presence/sub-key/" + pubnub.getConfiguration().getSubscribeKey() + "/channel/ch2," + + "ch1/heartbeat.*"))).stream().anyMatch(req -> !req.getQueryParams().containsKey("state"))); + + } + + @Test + public void testSubscribeChannelGroupBuilder() { + final AtomicBoolean atomic = new AtomicBoolean(false); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + + for (LoggedRequest request : requests) { + QueryParameter channelGroupQuery = request.queryParameter("channel-group"); + if (channelGroupQuery != null && channelGroupQuery.firstValue().equals("cg1,cg2")) { + atomic.set(true); + } + } + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channelGroups(Arrays.asList("cg1", "cg2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic); + } + + @Test + public void testSubscribeChannelGroupWithPresenceBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/,/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + + for (LoggedRequest request : requests) { + String[] channelGroups = request.queryParameter("channel-group").firstValue().split(","); + Arrays.sort(channelGroups); + if ("cg1,cg1-pnpres,cg2,cg2-pnpres".equals(joinArray(channelGroups))) { + atomic.addAndGet(1); + } + + } + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channelGroups(Arrays.asList("cg1", "cg2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribeWithFilterExpressionBuilder() { + final AtomicBoolean atomic = new AtomicBoolean(false); + + pubnub.getConfiguration().setFilterExpression("much=filtering"); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .withQueryParam("uuid", matching("myUUID")) + .withQueryParam("pnsdk", matching("PubNub-Java-Unified/suchJava")) + .withQueryParam("filter-expr", matching("much=filtering")) + .withQueryParam("tt", matching("0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() > 0); + atomic.set(true); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilTrue(atomic); + } + + @Test + public void testSubscribeWithEncryption() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14718972508742569\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":512,\"i\":\"ff374d0b-b866-40db-9ced-42d205bb808b\",\"p\":{\"t\":\"14718972508739738\"," + + "\"r\":1},\"k\":\"demo-36\",\"c\":\"max_ch1\",\"d\":\"6QoqmS9CnB3W9+I4mhmL7w==\"}]}"))); + + pubnub.getConfiguration().setCipherKey("hello"); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() > 0); + assertEquals("hey", pubnub.getMapper().elementToString(message.getMessage(), "text")); + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribeWithEncryptionPNOther() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14718972508742569\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":512,\"i\":\"ff374d0b-b866-40db-9ced-42d205bb808b\",\"p\":{\"t\":\"14718972508739738\"," + + "\"r\":1},\"k\":\"demo-36\",\"c\":\"max_ch1\"," + + "\"d\":{\"pn_other\":\"6QoqmS9CnB3W9+I4mhmL7w==\"}}]}"))); + + pubnub.getConfiguration().setCipherKey("hello"); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() > 0); + assertEquals("hey", message.getMessage().getAsJsonObject().get("pn_other").getAsJsonObject().get( + "text").getAsString()); + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribePresenceBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + assertTrue(requests.size() >= 1); + assertEquals("{\"text\":\"Enter Message Here\"}", message.getMessage().toString()); + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.Matchers.greaterThan(0)); + + } + + @Test + public void testSubscribePresencePayloadHereNowRefreshDeltaBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14901247588021627\",\"r\":2},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14901247587675704\",\"r\":1},\"k\":\"demo-36\"," + + "\"c\":\"moon-interval-deltas-pnpres\",\"d\":{\"action\": \"interval\", \"timestamp\": " + + "1490124758, \"occupancy\": 2, \"here_now_refresh\": true, \"join\": " + + "[\"2220E216-5A30-49AD-A89C-1E0B5AE26AD7\", \"4262AE3F-3202-4487-BEE0-1A0D91307DEB\"]}," + + "\"b\":\"moon-interval-deltas-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (atomic.get() == 0) { + assertEquals(true, presence.getHereNowRefresh()); + assertTrue(presence.getOccupancy().equals(2)); + atomic.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + + @Test + public void testSubscribePresencePayloadJoinDeltaBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14901247588021627\",\"r\":2},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14901247587675704\",\"r\":1},\"k\":\"demo-36\"," + + "\"c\":\"moon-interval-deltas-pnpres\",\"d\":{\"action\": \"interval\", \"timestamp\": " + + "1490124758, \"occupancy\": 2, \"join\": [\"2220E216-5A30-49AD-A89C-1E0B5AE26AD7\", " + + "\"4262AE3F-3202-4487-BEE0-1A0D91307DEB\"]},\"b\":\"moon-interval-deltas-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (atomic.get() == 0) { + List joinList = new ArrayList<>(); + joinList.add("2220E216-5A30-49AD-A89C-1E0B5AE26AD7"); + joinList.add("4262AE3F-3202-4487-BEE0-1A0D91307DEB"); + + assertEquals("interval", presence.getEvent()); + assertEquals(joinList, presence.getJoin()); + assertTrue(presence.getOccupancy().equals(2)); + atomic.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testSubscribePresencePayloadLeaveDeltaBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14901247588021627\",\"r\":2},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14901247587675704\",\"r\":1},\"k\":\"demo-36\"," + + "\"c\":\"moon-interval-deltas-pnpres\",\"d\":{\"action\": \"interval\", \"timestamp\": " + + "1490124758, \"occupancy\": 2, \"leave\": [\"2220E216-5A30-49AD-A89C-1E0B5AE26AD7\", " + + "\"4262AE3F-3202-4487-BEE0-1A0D91307DEB\"]},\"b\":\"moon-interval-deltas-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (atomic.get() == 0) { + List leaveList = new ArrayList<>(); + + leaveList.add("2220E216-5A30-49AD-A89C-1E0B5AE26AD7"); + leaveList.add("4262AE3F-3202-4487-BEE0-1A0D91307DEB"); + + assertEquals("interval", presence.getEvent()); + assertEquals(leaveList, presence.getLeave()); + assertTrue(presence.getOccupancy().equals(2)); + atomic.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testSubscribePresencePayloadTimeoutDeltaBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14901247588021627\",\"r\":2},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"p\":{\"t\":\"14901247587675704\",\"r\":1},\"k\":\"demo-36\"," + + "\"c\":\"moon-interval-deltas-pnpres\",\"d\":{\"action\": \"interval\", \"timestamp\": " + + "1490124758, \"occupancy\": 2, \"timeout\": [\"2220E216-5A30-49AD-A89C-1E0B5AE26AD7\", " + + "\"4262AE3F-3202-4487-BEE0-1A0D91307DEB\"]},\"b\":\"moon-interval-deltas-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (atomic.get() == 0) { + List timeoutList = new ArrayList<>(); + timeoutList.add("2220E216-5A30-49AD-A89C-1E0B5AE26AD7"); + timeoutList.add("4262AE3F-3202-4487-BEE0-1A0D91307DEB"); + + assertEquals("interval", presence.getEvent()); + assertEquals(timeoutList, presence.getTimeout()); + assertTrue(presence.getOccupancy().equals(2)); + atomic.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testSubscribePresencePayloadBuilder() { + final AtomicInteger atomic = new AtomicInteger(0); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14614512228786519\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"p\":{\"t\":\"14614512228418349\",\"r\":2}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":" + + "\"coolChannel-pnpres\",\"d\":{\"action\": \"join\", \"timestamp\": 1461451222, " + + "\"uuid\": \"4a6d5df7-e301-4e73-a7b7-6af9ab484eb0\", " + + "\"occupancy\": 1},\"b\":\"coolChannel-pnpres\"}]}\n"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (atomic.get() == 0) { + assertEquals("join", presence.getEvent()); + assertEquals("4a6d5df7-e301-4e73-a7b7-6af9ab484eb0", presence.getUuid()); + assertTrue(presence.getOccupancy().equals(1)); + assertTrue(presence.getTimestamp().equals(1461451222L)); + atomic.incrementAndGet(); + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(1)); + + } + + @Test + public void testSubscribePresenceStateCallback() { + final AtomicBoolean atomic = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch10,ch10-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14637536741734954\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":512,\"p\":{\"t\":\"14637536740940378\",\"r\":1}," + + "\"k\":\"demo-36\",\"c\":\"ch10-pnpres\",\"d\":" + + "{\"action\": \"join\", \"timestamp\": 1463753674, \"uuid\": " + + "\"24c9bb19-1fcd-4c40-a6f1-522a8a1329ef\", \"occupancy\": 3},\"b\":\"ch10-pnpres\"}" + + ",{\"a\":\"4\",\"f\":512,\"p\":{\"t\":\"14637536741726901\",\"r\":1},\"k\":\"" + + "demo-36\",\"c\":\"ch10-pnpres\",\"d\":{\"action\": \"state-change\", " + + "\"timestamp\": 1463753674, \"data\": {\"state\": \"cool\"}, " + + "\"uuid\": \"24c9bb19-1fcd-4c40-a6f1-522a8a1329ef\", " + + "\"occupancy\": 3},\"b\":\"ch10-pnpres\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + if (presence.getEvent().equals("state-change")) { + if (presence.getState().getAsJsonObject().has("state") && + presence.getState().getAsJsonObject().get("state").getAsString().equals("cool")) { + atomic.set(true); + } + } + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch10")).withPresence().execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(atomic, + org.hamcrest.core.IsEqual.equalTo(true)); + + } + + @Test + public void testSubscribeRegionBuilder() { + final AtomicBoolean atomic = new AtomicBoolean(); + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":8},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}" + + ",\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor(urlMatching("/v2/subscribe.*"))); + + if (requests.size() > 1) { + assertEquals("8", requests.get(1).queryParameter("tr").firstValue()); + atomic.set(true); + } + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(true)); + + } + + @Test + public void testRemoveListener() { + + final AtomicInteger atomic = new AtomicInteger(0); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + atomic.addAndGet(1); + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + atomic.addAndGet(1); + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + atomic.addAndGet(1); + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + pubnub.removeListener(sub1); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(2, TimeUnit.SECONDS) + .untilAtomic(atomic, org.hamcrest.core.IsEqual.equalTo(0)); + + } + + @Test + public void testUnsubscribe() throws InterruptedException { + final CountDownLatch statusReceived = new CountDownLatch(1); + final CountDownLatch messageReceived = new CountDownLatch(1); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"," + + " \"action\": \"leave\"}"))); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + pubnub.unsubscribe().channels(Arrays.asList("ch1")).execute(); + } + + List affectedChannels = status.getAffectedChannels(); + + assert affectedChannels != null; + + if (affectedChannels.size() == 1 && status.getOperation() == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels.get(0).equals("ch1")) { + statusReceived.countDown(); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + List requests = findAll(getRequestedFor( + urlMatching("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0.*"))); + + if (!requests.isEmpty()) { + messageReceived.countDown(); + } + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + assertTrue(statusReceived.await(2, TimeUnit.SECONDS)); + assertTrue(messageReceived.await(2, TimeUnit.SECONDS)); + } + + @Test + public void testAllHeartbeats() throws InterruptedException { + + pubnub.getConfiguration().setPresenceTimeout(20); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + final CountDownLatch statusReceived = new CountDownLatch(1); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"," + + " \"action\": \"leave\"}"))); + + pubnub.addListener(operationStatusReceivedListener(PNOperationType.PNHeartbeatOperation, statusReceived)); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + assertTrue(statusReceived.await(2, TimeUnit.SECONDS)); + } + + @Test + public void testAllHeartbeatsViaPresence() throws InterruptedException { + + pubnub.getConfiguration().setPresenceTimeout(20); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + final CountDownLatch statusReceived = new CountDownLatch(1); + + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"," + + " \"action\": \"leave\"}"))); + + pubnub.addListener(operationStatusReceivedListener(PNOperationType.PNHeartbeatOperation, statusReceived)); + + pubnub.presence().channels(Arrays.asList("ch1", "ch2")).connected(true).execute(); + + assertTrue(statusReceived.await(2, TimeUnit.SECONDS)); + } + + @Test + public void testAllHeartbeatsLeaveViaPresence() throws InterruptedException { + + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1,ch2/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\"," + + " \"action\": \"leave\"}"))); + final CountDownLatch statusReceived = new CountDownLatch(1); + + pubnub.addListener(operationStatusReceivedListener(PNOperationType.PNUnsubscribeOperation, statusReceived)); + + pubnub.presence().channels(Arrays.asList("ch1", "ch2")).connected(false).execute(); + + assertTrue(statusReceived.await(2, TimeUnit.SECONDS)); + } + + SubscribeCallback operationStatusReceivedListener(PNOperationType operationType, CountDownLatch statusReceived) { + return new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == operationType && !status.isError()) { + statusReceived.countDown(); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + } + + @Test + public void testSuccessOnFailureVerbosityHeartbeats() { + + final AtomicBoolean statusRecieved = new AtomicBoolean(); + pubnub.getConfiguration().setPresenceTimeout(20); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.FAILURES); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + statusRecieved.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult pnMessageResult) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult pnPresenceEventResult) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult pnSignalResult) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + + + }; + + pubnub.addListener(sub1); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(statusRecieved, + org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void testFailedHeartbeats() { + + final AtomicBoolean statusRecieved = new AtomicBoolean(); + pubnub.getConfiguration().setPresenceTimeout(20); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNHeartbeatOperation && status.isError()) { + statusRecieved.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(statusRecieved, + org.hamcrest.core.IsEqual.equalTo(true)); + } + + @Test + public void testSilencedHeartbeats() { + + final AtomicBoolean statusRecieved = new AtomicBoolean(); + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.NONE); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":" + + "[{\"a\":\"4\",\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}" + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\",\"d\":" + + "{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + statusRecieved.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAtomic(statusRecieved, + org.hamcrest.core.IsEqual.equalTo(false)); + } + + @Test + public void testFailedNoneHeartbeats() { + final AtomicBoolean statusRecieved = new AtomicBoolean(false); + + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.NONE); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres,ch1,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2,ch1/heartbeat")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (status.getOperation() != PNOperationType.PNHeartbeatOperation) { + statusRecieved.set(true); + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(statusRecieved); + } + + @Test + public void testHeartbeatsDisabled() { + final AtomicBoolean subscribeSuccess = new AtomicBoolean(); + final AtomicBoolean heartbeatFail = new AtomicBoolean(false); + + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.getConfiguration().getHeartbeatNotificationOptions()); + assertEquals(300, pubnub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubnub.getConfiguration().getHeartbeatInterval()); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) + .willReturn(aResponse() + .withBody("{\"t\":{\"t\":null,\"r\":12},\"m\":[]}") + .withStatus(200))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse() + .withStatus(200) + .withBody("{\"status\": 200, \"message\": \"OK\", \"service\":\"Presence\"}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (!status.isError()) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true); + } + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + heartbeatFail.set(true); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe() + .channels(Arrays.asList("ch1")) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until(() -> subscribeSuccess.get() && !heartbeatFail.get()); + } + + @Test + public void testHeartbeatsEnabled() { + final AtomicBoolean subscribeSuccess = new AtomicBoolean(); + final AtomicBoolean heartbeatSuccess = new AtomicBoolean(); + + pubnub.getConfiguration().setHeartbeatNotificationOptions(PNHeartbeatNotificationOptions.ALL); + + assertEquals(PNHeartbeatNotificationOptions.ALL, pubnub.getConfiguration().getHeartbeatNotificationOptions()); + assertEquals(300, pubnub.getConfiguration().getPresenceTimeout()); + assertEquals(0, pubnub.getConfiguration().getHeartbeatInterval()); + + pubnub.getConfiguration().setPresenceTimeout(20); + + assertEquals(20, pubnub.getConfiguration().getPresenceTimeout()); + assertEquals(9, pubnub.getConfiguration().getHeartbeatInterval()); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch1,ch1-pnpres/0")) + .willReturn(aResponse() + .withBody("{\"t\":{\"t\":null,\"r\":12},\"m\":[]}") + .withStatus(200))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/heartbeat")) + .willReturn(aResponse() + .withStatus(200) + .withBody("{\"status\": 200, \"message\": \"OK\", \"service\":\"Presence\"}"))); + + pubnub.addListener(new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + if (!status.isError()) { + if (status.getOperation() == PNOperationType.PNSubscribeOperation) { + subscribeSuccess.set(true); + } + if (status.getOperation() == PNOperationType.PNHeartbeatOperation) { + heartbeatSuccess.set(true); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }); + + pubnub.subscribe() + .channels(Collections.singletonList("ch1")) + .withPresence() + .execute(); + + Awaitility.await() + .atMost(5, TimeUnit.SECONDS) + .until(() -> subscribeSuccess.get() && heartbeatSuccess.get()); + } + + @Test + public void testMinimumPresenceValueNoInterval() { + pubnub.getConfiguration().setPresenceTimeout(10); + assertEquals(20, pubnub.getConfiguration().getPresenceTimeout()); + assertEquals(9, pubnub.getConfiguration().getHeartbeatInterval()); + } + + @Test + public void testMinimumPresenceValueWithInterval() { + pubnub.getConfiguration().setPresenceTimeoutWithCustomInterval(10, 50); + assertEquals(20, pubnub.getConfiguration().getPresenceTimeout()); + assertEquals(50, pubnub.getConfiguration().getHeartbeatInterval()); + } + + @Test + public void testUnsubscribeAll() { + final AtomicBoolean statusRecieved = new AtomicBoolean(false); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch1,ch2-pnpres,ch1-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/subscribe/mySubscribeKey/ch2,ch2-pnpres/0")) + .willReturn(aResponse().withBody("{\"t\":{\"t\":\"14607577960932487\",\"r\":1},\"m\":[{\"a\":\"4\"," + + "\"f\":0,\"i\":\"Client-g5d4g\",\"p\":{\"t\":\"14607577960925503\",\"r\":1}," + + "\"k\":\"sub-c-4cec9f8e-01fa-11e6-8180-0619f8945a4f\",\"c\":\"coolChannel\"," + + "\"d\":{\"text\":\"Enter Message Here\"},\"b\":\"coolChan-bnel\"}]}"))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch1/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + stubFor(get(urlPathEqualTo("/v2/presence/sub-key/mySubscribeKey/channel/ch2/leave")) + .willReturn(aResponse().withBody("{\"status\": 200, \"message\": \"OK\", \"service\": \"Presence\", " + + "\"action\": \"leave\"}"))); + + SubscribeCallback sub1 = new SubscribeCallback() { + @Override + public void status(@NotNull PubNub pubnub, @NotNull PNStatus status) { + + if (status.getCategory() == PNStatusCategory.PNConnectedCategory) { + pubnub.unsubscribe().channels(Arrays.asList("ch1")).execute(); + } + + assert status.getAffectedChannels() != null; + + List affectedChannels = status.getAffectedChannels(); + + if (affectedChannels != null && affectedChannels.size() == 1 && + status.getOperation() == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels.get(0).equals("ch1")) { + pubnub.unsubscribe().channels(Arrays.asList("ch2")).execute(); + } + } + + + if (affectedChannels != null && affectedChannels.size() == 1 && + status.getOperation() == PNOperationType.PNUnsubscribeOperation) { + if (affectedChannels.get(0).equals("ch2")) { + statusRecieved.set(true); + } + } + } + + @Override + public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) { + } + + @Override + public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presence) { + } + + @Override + public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult signal) { + + } + + @Override + public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) { + + } + + @Override + public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) { + + } + + @Override + public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) { + + } + + + @Override + public void messageAction(@NotNull PubNub pubnub, @NotNull PNMessageActionResult pnMessageActionResult) { + + } + + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + + } + }; + + pubnub.addListener(sub1); + pubnub.subscribe().channels(Arrays.asList("ch1", "ch2")).withPresence().execute(); + + Awaitility.await().atMost(4, TimeUnit.SECONDS).untilTrue(statusRecieved); + } + + + private String joinArray(String[] arr) { + StringBuilder builder = new StringBuilder(); + for (String s : arr) { + if (builder.length() != 0) { + builder.append(","); + } + builder.append(s); + } + return builder.toString(); + } +} diff --git a/src/test/java/com/pubnub/api/managers/subscription/utils/FakeCall.java b/src/test/java/com/pubnub/api/managers/subscription/utils/FakeCall.java new file mode 100644 index 000000000..32866770d --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/subscription/utils/FakeCall.java @@ -0,0 +1,90 @@ +package com.pubnub.api.managers.subscription.utils; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Slf4j +public class FakeCall implements Call { + private final ResponseSupplier responseSupplier; + private final Request request; + private final ExecutorService executor; + private boolean executed = false; + + public FakeCall(final Request request, final ResponseSupplier responseSupplier) { + this.request = request; + this.responseSupplier = responseSupplier; + this.executor = Executors.newSingleThreadExecutor(); + } + + @Override + public Response execute() throws IOException { + if (executed) throw new IllegalStateException("Already executed."); + executed = true; + + log.info("executing the call with request: " + request); + final RequestDetails requestDetails = request.tag(RequestDetails.class); + ResponseHolder responseHolder = responseSupplier.get(requestDetails); + final Exception exception = responseHolder.getException(); + if (exception == null) { + return responseHolder.getResponse(); + } else { + if (exception instanceof IOException) { + throw (IOException) exception; + } else { + throw new RuntimeException(exception); + } + } + } + + @Override + public void enqueue(final Callback callback) { + if (executed) throw new IllegalStateException("Already executed."); + executed = true; + + executor.execute(new Runnable() { + @Override + public void run() { + log.info("asynchronously executing the call with request: " + request); + final RequestDetails requestDetails = request.tag(RequestDetails.class); + final ResponseHolder responseHolder = responseSupplier.get(requestDetails); + final Exception exception = responseHolder.getException(); + if (exception == null) { + callback.onResponse(FakeCall.this, responseHolder.getResponse()); + } else { + callback.onFailure(FakeCall.this, exception); + } + } + }); + } + + @Override + public boolean isExecuted() { + return executed; + } + + @Override + public void cancel() { + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public Call clone() { + return new FakeCall(request, responseSupplier); + } + + @Override + public Request request() { + return request; + } +} diff --git a/src/test/java/com/pubnub/api/managers/subscription/utils/RequestDetails.java b/src/test/java/com/pubnub/api/managers/subscription/utils/RequestDetails.java new file mode 100644 index 000000000..a5b6c84c8 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/subscription/utils/RequestDetails.java @@ -0,0 +1,16 @@ +package com.pubnub.api.managers.subscription.utils; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +public abstract class RequestDetails { + @Data + @EqualsAndHashCode(callSuper=false) + public static class SubscribeRequestDetails extends RequestDetails { + private final String subscribeKey; + private final String channelCSV; + private final Map options; + } +} diff --git a/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseHolder.java b/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseHolder.java new file mode 100644 index 000000000..66c408e7a --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseHolder.java @@ -0,0 +1,22 @@ +package com.pubnub.api.managers.subscription.utils; + +import lombok.Getter; +import retrofit2.Response; + +public class ResponseHolder { + @Getter + private final Response response; + + @Getter + private final Exception exception; + + public ResponseHolder(final Response response) { + this.response = response; + this.exception = null; + } + + public ResponseHolder(final Exception exception) { + this.response = null; + this.exception = exception; + } +} diff --git a/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseSupplier.java b/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseSupplier.java new file mode 100644 index 000000000..20fe441e7 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/subscription/utils/ResponseSupplier.java @@ -0,0 +1,6 @@ +package com.pubnub.api.managers.subscription.utils; + +@FunctionalInterface +public interface ResponseSupplier { + ResponseHolder get(RequestDetails requestDetails); +} diff --git a/src/test/java/com/pubnub/api/managers/subscription/utils/SubscriptionTestUtils.java b/src/test/java/com/pubnub/api/managers/subscription/utils/SubscriptionTestUtils.java new file mode 100644 index 000000000..b5612605a --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/subscription/utils/SubscriptionTestUtils.java @@ -0,0 +1,64 @@ +package com.pubnub.api.managers.subscription.utils; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.models.server.SubscribeEnvelope; +import com.pubnub.api.services.SubscribeService; +import lombok.SneakyThrows; +import okhttp3.Request; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.jetbrains.annotations.NotNull; +import org.mockito.ArgumentCaptor; + +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SubscriptionTestUtils { + @SneakyThrows + public static PubNub pubnub(final RetrofitManager retrofitManager) { + final PNConfiguration pnConfiguration = new PNConfiguration(); + pnConfiguration.setSubscribeKey("fake_sub_key"); + pnConfiguration.setConnectTimeout(1); + + final PubNub pubnub = new PubNub(pnConfiguration); + FieldUtils.writeField(pubnub, "retrofitManager", retrofitManager, true); + return pubnub; + } + + @NotNull + @SneakyThrows + public static TelemetryManager telemetryManager(final PubNub pubnub) { + return (TelemetryManager) FieldUtils.readField(pubnub, "telemetryManager", true); + } + + @NotNull + public static RetrofitManager retrofitManagerMock(final ResponseSupplier responseSupplier) { + final SubscribeService subscribeServiceMock = mock(SubscribeService.class); + + final ArgumentCaptor subscribeKeyCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor channelCSVCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor> optionsCaptor = ArgumentCaptor.forClass(Map.class); + + when(subscribeServiceMock.subscribe(subscribeKeyCaptor.capture(), channelCSVCaptor.capture(), optionsCaptor.capture())) + .thenAnswer(invocation -> { + final String subscribeKey = subscribeKeyCaptor.getValue(); + final String channelCSV = channelCSVCaptor.getValue(); + final Map options = optionsCaptor.getValue(); + + final Request.Builder requestBuilder = new Request.Builder(); + final Request request = requestBuilder.get() + .url("Http://example.com/" + channelCSV + "/" + options) + .tag(RequestDetails.class, new RequestDetails.SubscribeRequestDetails(subscribeKey, channelCSV, options)).build(); + + return new FakeCall<>(request, responseSupplier); + }); + + final RetrofitManager retrofitManagerMock = mock(RetrofitManager.class); + when(retrofitManagerMock.getSubscribeService()).thenReturn(subscribeServiceMock); + return retrofitManagerMock; + } +} diff --git a/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java b/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java new file mode 100644 index 000000000..44dd9de29 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java @@ -0,0 +1,21 @@ +package com.pubnub.api.managers.token_manager; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.*; + +public class TokenParserTest { + + @Test + public void parseTokenWithMeta() throws PubNubException { + String tokenWithMeta = "qEF2AkF0GmFLd-NDdHRsGQWgQ3Jlc6VEY2hhbqFjY2gxGP9DZ3JwoWNjZzEY_0N1c3KgQ3NwY6BEdXVpZKFldXVpZDEY_0NwYXSlRGNoYW6gQ2dycKBDdXNyoENzcGOgRHV1aWShYl4kAURtZXRho2VzY29yZRhkZWNvbG9yY3JlZGZhdXRob3JlcGFuZHVEdXVpZGtteWF1dGh1dWlkMUNzaWdYIP2vlxHik0EPZwtgYxAW3-LsBaX_WgWdYvtAXpYbKll3"; + PNToken parsed = new TokenParser().unwrapToken(tokenWithMeta); + Assert.assertNotNull(parsed.getMeta()); + assertFalse(((Map) parsed.getMeta()).isEmpty()); + } +} diff --git a/src/test/java/com/pubnub/api/vendor/CryptoTest.java b/src/test/java/com/pubnub/api/vendor/CryptoTest.java new file mode 100644 index 000000000..575f7f6b2 --- /dev/null +++ b/src/test/java/com/pubnub/api/vendor/CryptoTest.java @@ -0,0 +1,50 @@ +package com.pubnub.api.vendor; + +import com.pubnub.api.PubNubException; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Random; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +public class CryptoTest { + private static final int MAX_FILE_SIZE_IN_BYTES = 1024 * 1024 * 5; + + @Test + public void canDecryptWhatIsEncrypted() throws IOException, PubNubException { + //given + final String cipherKey = "enigma"; + final byte[] byteArrayToEncrypt = byteArrayToEncrypt(); + byte[] decryptedByteArray; + + //when + final byte[] encryptedByteArray = FileEncryptionUtil.encryptToBytes(cipherKey, + byteArrayToEncrypt); + try (InputStream decryptedInputStream = FileEncryptionUtil.decrypt(cipherKey, + new ByteArrayInputStream(encryptedByteArray))) { + try (final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + IOUtils.copy(decryptedInputStream, byteArrayOutputStream); + decryptedByteArray = byteArrayOutputStream.toByteArray(); + } + } + + //then + assertThat(decryptedByteArray, allOf( + equalTo(byteArrayToEncrypt), + not(equalTo(encryptedByteArray)))); + } + + private byte[] byteArrayToEncrypt() { + final Random random = new Random(); + final int fileSize = random.nextInt(MAX_FILE_SIZE_IN_BYTES); + byte[] fileContents = new byte[fileSize]; + random.nextBytes(fileContents); + return fileContents; + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/api/vendor/EncryptDecryptTest.java b/src/test/java/com/pubnub/api/vendor/EncryptDecryptTest.java new file mode 100644 index 000000000..cab85f579 --- /dev/null +++ b/src/test/java/com/pubnub/api/vendor/EncryptDecryptTest.java @@ -0,0 +1,82 @@ +package com.pubnub.api.vendor; + +import com.pubnub.api.PubNubException; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +public class EncryptDecryptTest { + @Test + public void canDecryptTextWhatIsEncryptedWithStaticIV() throws IOException, PubNubException { + //given + final String cipherKey = "enigma"; + final String msgToEncrypt = "Hello world"; + + + //when + Crypto crypto = new Crypto(cipherKey); + final String encryptedMsg = crypto.encrypt(msgToEncrypt); + String decryptedMsg = crypto.decrypt(encryptedMsg); + + //then + Assert.assertEquals(msgToEncrypt, decryptedMsg); + } + + @Test + public void canDecryptTextWhatIsEncryptedWithRandomIV() throws IOException, PubNubException { + //given + final String cipherKey = "enigma"; + final String msgToEncrypt = "Hello world"; + + + //when + Crypto crypto = new Crypto(cipherKey, true); + final String encryptedMsg = crypto.encrypt(msgToEncrypt); + String decryptedMsg = crypto.decrypt(encryptedMsg); + + //then + Assert.assertEquals(msgToEncrypt, decryptedMsg); + } + + @Test + public void encryptingWithRandomIVTwoTimesTheSameMessageProducesDifferentOutput() throws PubNubException { + //given + final String cipherKey = "enigma"; + final String msgToEncrypt = "Hello world"; + + //when + Crypto crypto = new Crypto(cipherKey, true); + final String encrypted1 = crypto.encrypt(msgToEncrypt); + String encrypted2 = crypto.encrypt(msgToEncrypt); + + //then + Assert.assertNotEquals(encrypted1, encrypted2); + } + + @Test + public void encryptingWithRandomIVTwoTimesDecryptedMsgIsTheSame() throws PubNubException { + //given + final String cipherKey = "enigma"; + final String msgToEncrypt = "Hello world"; + + //when + Crypto crypto = new Crypto(cipherKey, true); + final String encrypted1 = crypto.encrypt(msgToEncrypt); + String encrypted2 = crypto.encrypt(msgToEncrypt); + + //then + Assert.assertEquals(msgToEncrypt, crypto.decrypt(encrypted1)); + Assert.assertEquals(msgToEncrypt, crypto.decrypt(encrypted2)); + } + + +} diff --git a/src/test/java/com/pubnub/api/workers/SubscribeMessageWorkerTest.java b/src/test/java/com/pubnub/api/workers/SubscribeMessageWorkerTest.java new file mode 100644 index 000000000..9510734e0 --- /dev/null +++ b/src/test/java/com/pubnub/api/workers/SubscribeMessageWorkerTest.java @@ -0,0 +1,210 @@ +package com.pubnub.api.workers; + +import com.google.gson.Gson; +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubUtil; +import com.pubnub.api.callbacks.SubscribeCallback; +import com.pubnub.api.managers.DuplicationManager; +import com.pubnub.api.managers.ListenerManager; +import com.pubnub.api.models.consumer.pubsub.files.PNFileEventResult; +import com.pubnub.api.models.server.SubscribeEnvelope; +import com.pubnub.api.models.server.SubscribeMessage; +import okhttp3.HttpUrl; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class SubscribeMessageWorkerTest { + private final ExecutorService executor = Executors.newCachedThreadPool(); + private final SubscribeMessage subscribeMessage = subscribeMessage(); + private final String authKey = "ak"; + + @Test + public void fileEventUrlContainsAuthQueryParamWhenAuthIsSet() throws InterruptedException { + //given + PNConfiguration config = configWithAuth(config()); + PubNub pubnub = new PubNub(config); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + ListenerManager listenerManager = new ListenerManager(pubnub); + SubscribeMessageWorker subscribeMessageWorker = subscribeMessageWorker(pubnub, + listenerManager, + queue + ); + AtomicReference fileEventResult = new AtomicReference<>(); + CountDownLatch receivedLatch = new CountDownLatch(1); + listenerManager.addListener(capturingFileEventListener(fileEventResult, receivedLatch)); + + + //when + executor.execute(subscribeMessageWorker); + queue.offer(subscribeMessage); + + //then + if (!receivedLatch.await(5, TimeUnit.SECONDS)) { + Assert.fail("Message was not received"); + } + Map queryParams = queryParams(fileEventResult.get().getFile().getUrl()); + Assert.assertEquals(setOf(PubNubUtil.AUTH_QUERY_PARAM_NAME), queryParams.keySet()); + } + + @Test + public void fileEventUrlContainsNoQueryParamsWhenNoSecretNorAuth() throws InterruptedException { + //given + PNConfiguration config = config(); + PubNub pubnub = new PubNub(config); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + ListenerManager listenerManager = new ListenerManager(pubnub); + SubscribeMessageWorker subscribeMessageWorker = subscribeMessageWorker(pubnub, + listenerManager, + queue + ); + AtomicReference fileEventResult = new AtomicReference<>(); + CountDownLatch receivedLatch = new CountDownLatch(1); + listenerManager.addListener(capturingFileEventListener(fileEventResult, receivedLatch)); + + + //when + executor.execute(subscribeMessageWorker); + queue.offer(subscribeMessage); + + //then + if (!receivedLatch.await(5, TimeUnit.SECONDS)) { + Assert.fail("Message was not received"); + } + Map queryParams = queryParams(fileEventResult.get().getFile().getUrl()); + Assert.assertEquals(Collections.emptyMap(), queryParams); + } + + @Test + public void fileEventUrlContainsSignatureQueryParamWhenSecretIsSet() throws InterruptedException { + //given + PNConfiguration config = configWithSecret(config()); + PubNub pubnub = new PubNub(config); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + ListenerManager listenerManager = new ListenerManager(pubnub); + SubscribeMessageWorker subscribeMessageWorker = subscribeMessageWorker(pubnub, + listenerManager, + queue + ); + AtomicReference fileEventResult = new AtomicReference<>(); + CountDownLatch receivedLatch = new CountDownLatch(1); + listenerManager.addListener(capturingFileEventListener(fileEventResult, receivedLatch)); + + //when + executor.execute(subscribeMessageWorker); + queue.offer(subscribeMessage); + + //then + if (!receivedLatch.await(5, TimeUnit.SECONDS)) { + Assert.fail("Message was not received"); + } + Map queryParams = queryParams(fileEventResult.get().getFile().getUrl()); + Assert.assertEquals(setOf(PubNubUtil.SIGNATURE_QUERY_PARAM_NAME, PubNubUtil.TIMESTAMP_QUERY_PARAM_NAME), queryParams.keySet()); + } + + @Test + public void fileEventUrlContainsSignatureAndAuthQueryParamsWhenAuthAndSecretAreSet() throws InterruptedException { + //given + PNConfiguration config = configWithAuth(configWithSecret(config())); + PubNub pubnub = new PubNub(config); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + ListenerManager listenerManager = new ListenerManager(pubnub); + SubscribeMessageWorker subscribeMessageWorker = subscribeMessageWorker(pubnub, + listenerManager, + queue + ); + AtomicReference fileEventResult = new AtomicReference<>(); + CountDownLatch receivedLatch = new CountDownLatch(1); + listenerManager.addListener(capturingFileEventListener(fileEventResult, receivedLatch)); + + //when + executor.execute(subscribeMessageWorker); + queue.offer(subscribeMessage); + + //then + if (!receivedLatch.await(5, TimeUnit.SECONDS)) { + Assert.fail("Message was not received"); + } + Map queryParams = queryParams(fileEventResult.get().getFile().getUrl()); + System.out.println(fileEventResult.get().getFile().getUrl()); + Assert.assertEquals(setOf(PubNubUtil.SIGNATURE_QUERY_PARAM_NAME, + PubNubUtil.TIMESTAMP_QUERY_PARAM_NAME, + PubNubUtil.AUTH_QUERY_PARAM_NAME), queryParams.keySet()); + } + + private Set setOf(String... values) { + return new HashSet<>(Arrays.asList(values.clone())); + } + + private SubscribeCallback.BaseSubscribeCallback capturingFileEventListener(AtomicReference fileEventResult, + CountDownLatch receivedLatch) { + return new SubscribeCallback.BaseSubscribeCallback() { + @Override + public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult pnFileEventResult) { + fileEventResult.set(pnFileEventResult); + receivedLatch.countDown(); + } + }; + } + + private SubscribeMessageWorker subscribeMessageWorker(PubNub pubnub, + ListenerManager listenerManager, + LinkedBlockingQueue queue) { + return new SubscribeMessageWorker(pubnub, + listenerManager, + queue, + new DuplicationManager(pubnub.getConfiguration()) + ); + } + + private PNConfiguration config() { + PNConfiguration config = new PNConfiguration(); + config.setPublishKey("pk"); + config.setSubscribeKey("ck"); + return config; + } + + private PNConfiguration configWithAuth(PNConfiguration config) { + config.setAuthKey(authKey); + return config; + } + + private PNConfiguration configWithSecret(PNConfiguration config) { + config.setSecretKey("sk"); + return config; + } + + private SubscribeMessage subscribeMessage() { + Gson gson = new Gson(); + Scanner s = new Scanner(SubscribeMessageWorkerTest.class.getResourceAsStream("/fileEvent.json")).useDelimiter( + "\\A"); + String result = s.hasNext() ? s.next() : ""; + SubscribeEnvelope envelope = gson.fromJson(result, SubscribeEnvelope.class); + Assert.assertEquals(1, envelope.getMessages().size()); + return envelope.getMessages().get(0); + } + + private Map queryParams(String urlString) { + Map queryParameters = new HashMap<>(); + HttpUrl httpUrl = HttpUrl.get(urlString); + for (String paramName : httpUrl.queryParameterNames()) { + queryParameters.put(paramName, httpUrl.queryParameter(paramName)); + } + return queryParameters; + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/ContractTestConfig.kt b/src/test/java/com/pubnub/contract/ContractTestConfig.kt new file mode 100644 index 000000000..8c28af42a --- /dev/null +++ b/src/test/java/com/pubnub/contract/ContractTestConfig.kt @@ -0,0 +1,25 @@ +package com.pubnub.contract + +import org.aeonbits.owner.Config +import org.aeonbits.owner.Config.Sources +import org.aeonbits.owner.ConfigFactory + +@Sources("file:test.properties") +interface ContractTestConfig : Config { + @Config.Key("pamSubKey") + fun pamSubKey(): String? + + @Config.Key("pamPubKey") + fun pamPubKey(): String? + + @Config.Key("pamSecKey") + fun pamSecKey(): String? + + @Config.Key("serverHostPort") + fun serverHostPort(): String + + @Config.Key("serverMock") + fun serverMock(): Boolean +} + +val CONTRACT_TEST_CONFIG: ContractTestConfig = ConfigFactory.create(ContractTestConfig::class.java, System.getenv()) diff --git a/src/test/java/com/pubnub/contract/Hooks.kt b/src/test/java/com/pubnub/contract/Hooks.kt new file mode 100644 index 000000000..c9f5398e1 --- /dev/null +++ b/src/test/java/com/pubnub/contract/Hooks.kt @@ -0,0 +1,59 @@ +package com.pubnub.contract + +import io.cucumber.java.After +import io.cucumber.java.Before +import io.cucumber.java.Scenario +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.junit.Assert.fail +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +class Hooks { + private val interceptor = HttpLoggingInterceptor(); + private val mockPubnubService: MockPubnubService = Retrofit.Builder() + .client(OkHttpClient.Builder().addInterceptor(interceptor).build()) + .baseUrl("http://" + CONTRACT_TEST_CONFIG.serverHostPort()) + .addConverterFactory(GsonConverterFactory.create()) + .build().create(MockPubnubService::class.java) + + @Before + fun before(scenario: Scenario) { + if (!CONTRACT_TEST_CONFIG.serverMock()) { + return + } + scenario.contractName()?.let { + mockPubnubService.init(options = mapOf("__contract__script__" to it)).execute() + } + } + + @After + fun after(scenario: Scenario) { + if (!CONTRACT_TEST_CONFIG.serverMock()) { + return + } + scenario.contractName()?.let { + val responseBody = mockPubnubService.expect().execute().body() + if (responseBody == null) { + fail("Expect response body is null") + } else { + if (responseBody.expectations.pending.isNotEmpty() || + responseBody.expectations.failed.isNotEmpty()) { + fail("""Scenario ${responseBody.contract} considered failure: + pending - ${responseBody.expectations.pending.joinToString() }, + failed - ${responseBody.expectations.failed.joinToString() }""".trimIndent()) + } + } + + } + } + + private fun Scenario.contractName(): String? { + return sourceTagNames + .filter { it: String -> it.startsWith("@contract") } + .map { it: String -> + it.split("=").toTypedArray()[1] + } + .firstOrNull() + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/MockPubnubService.kt b/src/test/java/com/pubnub/contract/MockPubnubService.kt new file mode 100644 index 000000000..c30d119b4 --- /dev/null +++ b/src/test/java/com/pubnub/contract/MockPubnubService.kt @@ -0,0 +1,26 @@ +package com.pubnub.contract + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.QueryMap + +interface MockPubnubService { + + @GET("init") + fun init( + @QueryMap options: Map + ): Call + + @GET("expect") + fun expect(): Call +} + +data class ExpectResponse( + val contract: String, + val expectations: Expectations +) { + data class Expectations( + val pending: List, + val failed: List + ) +} diff --git a/src/test/java/com/pubnub/contract/access/parameter/PermissionType.kt b/src/test/java/com/pubnub/contract/access/parameter/PermissionType.kt new file mode 100644 index 000000000..f69c967ff --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/parameter/PermissionType.kt @@ -0,0 +1,19 @@ +package com.pubnub.contract.access.parameter + +import io.cucumber.java.ParameterType + +enum class PermissionType { + READ, + WRITE, + GET, + MANAGE, + UPDATE, + JOIN, + DELETE, +} + + +@ParameterType(".*") +fun permissionType(name: String): PermissionType { + return PermissionType.valueOf(name) +} diff --git a/src/test/java/com/pubnub/contract/access/parameter/ResourceType.kt b/src/test/java/com/pubnub/contract/access/parameter/ResourceType.kt new file mode 100644 index 000000000..7dcbedb86 --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/parameter/ResourceType.kt @@ -0,0 +1,31 @@ +package com.pubnub.contract.access.parameter + +import com.pubnub.api.models.consumer.access_manager.v3.PNToken +import io.cucumber.java.ParameterType + +enum class ResourceType { + CHANNEL, + CHANNEL_GROUP, + UUID +} + +@ParameterType(".*") +fun resourceType(name: String): ResourceType { + return ResourceType.valueOf(name) +} + +fun PNToken.resourcePermissionsMap(resourceType: ResourceType): Map { + return when (resourceType) { + ResourceType.CHANNEL -> resources.channels + ResourceType.CHANNEL_GROUP -> resources.channelGroups + ResourceType.UUID -> resources.uuids + } +} + +fun PNToken.patternPermissionsMap(resourceType: ResourceType): Map { + return when (resourceType) { + ResourceType.CHANNEL -> patterns.channels + ResourceType.CHANNEL_GROUP -> patterns.channelGroups + ResourceType.UUID -> patterns.uuids + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/access/parameter/TTLType.kt b/src/test/java/com/pubnub/contract/access/parameter/TTLType.kt new file mode 100644 index 000000000..bc7e89a66 --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/parameter/TTLType.kt @@ -0,0 +1,8 @@ +package com.pubnub.contract.access.parameter + +import io.cucumber.java.ParameterType + +@ParameterType(".*") +fun ttl(ttl: String): Long { + return ttl.toLong() +} diff --git a/src/test/java/com/pubnub/contract/access/state/GrantTokenState.kt b/src/test/java/com/pubnub/contract/access/state/GrantTokenState.kt new file mode 100644 index 000000000..78be5490a --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/state/GrantTokenState.kt @@ -0,0 +1,19 @@ +package com.pubnub.contract.access.state + +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult +import com.pubnub.api.models.consumer.access_manager.v3.PNResource +import com.pubnub.api.models.consumer.access_manager.v3.PNToken + +class GrantTokenState { + var parsedToken: PNToken? = null + var TTL: Long? = null + var result: PNGrantTokenResult? = null + var authorizedUUID: String? = null + var definedGrants: MutableList> = mutableListOf() + var currentGrant: PNResource<*>? = null + set(value) { + if (value != null) definedGrants.add(value) + field = value + } + var currentResourcePermissions: PNToken.PNResourcePermissions? = null +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/access/step/GivenSteps.kt b/src/test/java/com/pubnub/contract/access/step/GivenSteps.kt new file mode 100644 index 000000000..77a1d5a0e --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/step/GivenSteps.kt @@ -0,0 +1,107 @@ +package com.pubnub.contract.access.step + +import com.pubnub.contract.access.parameter.PermissionType +import com.pubnub.contract.access.parameter.ResourceType +import com.pubnub.contract.access.state.GrantTokenState +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult +import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant +import io.cucumber.java.PendingException +import io.cucumber.java.en.And +import io.cucumber.java.en.Given + +class GivenSteps(private val grantTokenState: GrantTokenState) { + private val tokenWithAll = "qEF2AkF0GmEI03xDdHRsGDxDcmVzpURjaGFuoWljaGFubmVsLTEY70NncnChb2NoYW5uZWxfZ3JvdXAtMQVDdXNyoENzcGOgRHV1aWShZnV1aWQtMRhoQ3BhdKVEY2hhbqFtXmNoYW5uZWwtXFMqJBjvQ2dycKF0XjpjaGFubmVsX2dyb3VwLVxTKiQFQ3VzcqBDc3BjoER1dWlkoWpedXVpZC1cUyokGGhEbWV0YaBEdXVpZHR0ZXN0LWF1dGhvcml6ZWQtdXVpZENzaWdYIPpU-vCe9rkpYs87YUrFNWkyNq8CVvmKwEjVinnDrJJc" + + @Given("I have a known token containing UUID pattern Permissions") + fun i_have_a_known_token_containing_uuid_pattern_permissions() { + // Write code here that turns the phrase above into concrete actions + grantTokenState.result = PNGrantTokenResult(tokenWithAll) + } + + @Given("I have a known token containing UUID resource permissions") + fun i_have_a_known_token_containing_uuid_resource_permissions() { + // Write code here that turns the phrase above into concrete actions + grantTokenState.result = PNGrantTokenResult(tokenWithAll) + } + + @Given("I have a known token containing an authorized UUID") + fun i_have_a_known_token_containing_an_authorized_uuid() { + // Write code here that turns the phrase above into concrete actions + grantTokenState.result = PNGrantTokenResult(tokenWithAll) + } + + @Given("the {string} {resourceType} pattern access permissions") + fun the_channel_pattern_access_permissions(pattern: String, resourceType: ResourceType) { + when (resourceType) { + ResourceType.CHANNEL -> grantTokenState.currentGrant = ChannelGrant.pattern(pattern) + ResourceType.CHANNEL_GROUP -> grantTokenState.currentGrant = ChannelGroupGrant.pattern(pattern) + ResourceType.UUID -> grantTokenState.currentGrant = UUIDGrant.pattern(pattern) + } + } + + + @Given("the {string} {resourceType} resource access permissions") + fun the_channel_resource_access_permissions(name: String, resourceType: ResourceType) { + when (resourceType) { + ResourceType.CHANNEL -> grantTokenState.currentGrant = ChannelGrant.name(name) + ResourceType.CHANNEL_GROUP -> grantTokenState.currentGrant = ChannelGroupGrant.id(name) + ResourceType.UUID -> grantTokenState.currentGrant = UUIDGrant.id(name) + } + + } + + @Given("the TTL {ttl}") + fun the_ttl(ttl: Long) { + grantTokenState.TTL = ttl + } + + @Given("deny resource permission GET") + fun deny_resource_permission_get() { + //in grant token everything is denied by default + } + + @And("grant resource permission {permissionType}") + fun grant_resource_permission(permissionType: PermissionType) { + grant_permission(permissionType) + } + + @And("grant pattern permission {permissionType}") + fun grant_pattern_permission(permissionType: PermissionType) { + grant_permission(permissionType) + } + + private fun grant_permission(permissionType: PermissionType) { + when (permissionType) { + PermissionType.READ -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.read() } + is ChannelGroupGrant -> { currentGrant.read() } + } + PermissionType.WRITE -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.write() } + } + PermissionType.GET -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.get() } + is UUIDGrant -> { currentGrant.get() } + } + PermissionType.MANAGE -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.manage() } + is ChannelGroupGrant -> { currentGrant.manage() } + } + PermissionType.UPDATE -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.update() } + is UUIDGrant -> { currentGrant.update() } + } + PermissionType.JOIN -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.join() } + } + PermissionType.DELETE -> when (val currentGrant = grantTokenState.currentGrant) { + is ChannelGrant -> { currentGrant.delete() } + is UUIDGrant -> { currentGrant.delete() } + } + } + } + + +} diff --git a/src/test/java/com/pubnub/contract/access/step/ThenSteps.kt b/src/test/java/com/pubnub/contract/access/step/ThenSteps.kt new file mode 100644 index 000000000..22f16354f --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/step/ThenSteps.kt @@ -0,0 +1,102 @@ +package com.pubnub.contract.access.step + +import com.pubnub.contract.access.parameter.PermissionType +import com.pubnub.contract.access.parameter.ResourceType +import com.pubnub.contract.access.parameter.patternPermissionsMap +import com.pubnub.contract.access.parameter.resourcePermissionsMap +import com.pubnub.contract.access.state.GrantTokenState +import com.pubnub.contract.state.World +import com.pubnub.api.models.consumer.access_manager.v3.PNToken +import io.cucumber.java.en.Then +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +class ThenSteps( + private val grantTokenState: GrantTokenState, + private val world: World +) { + @Then("the authorized UUID {string}") + fun authorized_uuid(uuid: String) { + grantTokenState.authorizedUUID = uuid + } + + @Then("the parsed token output contains the authorized UUID {string}") + fun the_parsed_token_output_contains_the_authorized_uuid(uuid: String) { + val token = grantTokenState.parsedToken!! + MatcherAssert.assertThat(token.authorizedUUID, Matchers.`is`(uuid)) + } + + @Then("the token contains the authorized UUID {string}") + fun the_token_contains_the_authorized_uuid(uuid: String) { + val result = grantTokenState.result!! + val parsedToken = world.pubnub.parseToken(result.token) + MatcherAssert.assertThat(parsedToken.authorizedUUID, Matchers.`is`(uuid)) + } + + + @Then("the token contains the TTL {ttl}") + fun the_token_contains_the_ttl(ttl: Long) { + val result = grantTokenState.result!! + val token = world.pubnub.parseToken(result.token) + MatcherAssert.assertThat(token.ttl, Matchers.`is`(ttl)) + } + + @Then("the token does not contain an authorized uuid") + fun the_token_does_not_contain_an_authorized_uuid() { + val result = grantTokenState.result!! + val token = world.pubnub.parseToken(result.token) + MatcherAssert.assertThat(token.authorizedUUID, Matchers.nullValue()) + } + + @Then("the token has {string} {resourceType} resource access permissions") + fun the_token_has_channel_resource_access_permissions(name: String, resourceType: ResourceType) { + val token = parsedToken()!! + val permissions = token.resourcePermissionsMap(resourceType)[name] + MatcherAssert.assertThat( + "Token doesn't contain required permissions $token", + permissions, + Matchers.notNullValue() + ) + grantTokenState.currentResourcePermissions = permissions + } + + @Then("the token has {string} {resourceType} pattern access permissions") + fun the_token_has_channel_pattern_access_permissions(name: String, resourceType: ResourceType) { + val token = parsedToken()!! + val permissions = token.patternPermissionsMap(resourceType)[name] + MatcherAssert.assertThat( + "Token doesn't contain required permissions $token", + permissions, + Matchers.notNullValue() + ) + grantTokenState.currentResourcePermissions = permissions + } + + @Then("token resource permission {permissionType}") + fun token_resource_permission(permissionType: PermissionType) { + assertPermissions(permissionType) + } + + @Then("token pattern permission {permissionType}") + fun token_pattern_permission(permissionType: PermissionType) { + assertPermissions(permissionType) + } + + private fun assertPermissions(permissionType: PermissionType) { + val permissions = grantTokenState.currentResourcePermissions!! + when (permissionType) { + PermissionType.READ -> MatcherAssert.assertThat(permissions.isRead, Matchers.`is`(true)) + PermissionType.WRITE -> MatcherAssert.assertThat(permissions.isWrite, Matchers.`is`(true)) + PermissionType.GET -> MatcherAssert.assertThat(permissions.isGet, Matchers.`is`(true)) + PermissionType.MANAGE -> MatcherAssert.assertThat(permissions.isManage, Matchers.`is`(true)) + PermissionType.UPDATE -> MatcherAssert.assertThat(permissions.isUpdate, Matchers.`is`(true)) + PermissionType.JOIN -> MatcherAssert.assertThat(permissions.isJoin, Matchers.`is`(true)) + PermissionType.DELETE -> MatcherAssert.assertThat(permissions.isDelete, Matchers.`is`(true)) + } + + } + + private fun parsedToken(): PNToken? { + return grantTokenState.parsedToken ?: grantTokenState.result?.let { world.pubnub.parseToken(it.token) } + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/access/step/WhenSteps.kt b/src/test/java/com/pubnub/contract/access/step/WhenSteps.kt new file mode 100644 index 000000000..82aa2a581 --- /dev/null +++ b/src/test/java/com/pubnub/contract/access/step/WhenSteps.kt @@ -0,0 +1,51 @@ +package com.pubnub.contract.access.step + +import com.pubnub.contract.access.state.GrantTokenState +import com.pubnub.contract.state.World +import com.pubnub.api.PubNubException +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant +import com.pubnub.api.models.consumer.access_manager.v3.UUIDGrant +import io.cucumber.java.en.When +import org.junit.Assert + +class WhenSteps( + private val grantTokenState: GrantTokenState, + private val world: World +) { + + + @When("I grant a token specifying those permissions") + fun grant_token() { + grantTokenState.result = world.pubnub.grantToken().let { + grantTokenState.TTL?.let { ttl -> + it.ttl(ttl.toInt()) + } + grantTokenState.authorizedUUID?.let { authorizedUUID -> it.authorizedUUID(authorizedUUID) } + it.channels(grantTokenState.definedGrants.filterIsInstance(ChannelGrant::class.java)) + it.channelGroups(grantTokenState.definedGrants.filterIsInstance(ChannelGroupGrant::class.java)) + it.uuids(grantTokenState.definedGrants.filterIsInstance(UUIDGrant::class.java)) + + it.sync() + } + } + + @When("I attempt to grant a token specifying those permissions") + fun i_attempt_to_grant_a_token_specifying_those_permissions() { + try { + grant_token() + Assert.fail("Expected exception") + } catch (ex: PubNubException) { + world.pnException = ex + } catch (ex: AssertionError) { + throw ex + } catch (t: Throwable) { + Assert.fail("Expected PubNubException but got throwable $t") + } + } + + @When("I parse the token") + fun i_parse_the_token() { + grantTokenState.parsedToken = world.pubnub.parseToken(grantTokenState.result?.token) + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/state/World.kt b/src/test/java/com/pubnub/contract/state/World.kt new file mode 100644 index 000000000..ef138453d --- /dev/null +++ b/src/test/java/com/pubnub/contract/state/World.kt @@ -0,0 +1,17 @@ +package com.pubnub.contract.state + +import com.pubnub.contract.CONTRACT_TEST_CONFIG +import com.pubnub.api.PNConfiguration +import com.pubnub.api.PubNub +import com.pubnub.api.PubNubException +import com.pubnub.api.enums.PNLogVerbosity + +class World { + val configuration: PNConfiguration by lazy { PNConfiguration().apply { + origin = CONTRACT_TEST_CONFIG.serverHostPort() + isSecure = false + logVerbosity = PNLogVerbosity.BODY + } } + val pubnub: PubNub by lazy { PubNub(configuration) } + var pnException: PubNubException? = null +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/step/ErrorMessageAndDetailsStep.kt b/src/test/java/com/pubnub/contract/step/ErrorMessageAndDetailsStep.kt new file mode 100644 index 000000000..9d52851b9 --- /dev/null +++ b/src/test/java/com/pubnub/contract/step/ErrorMessageAndDetailsStep.kt @@ -0,0 +1,16 @@ +package com.pubnub.contract.step + +import com.pubnub.contract.state.World +import io.cucumber.java.en.Then +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers + +class ErrorMessageAndDetailsStep(private val world: World) { + @Then("I see the error message {string} and details {string}") + fun i_see_the_error_message_and_details(message: String, details: String) { + val exception = world.pnException!! + assertThat(exception.errormsg, Matchers.containsString(message)) + assertThat(exception.errormsg, Matchers.containsString(details)) + } + +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/step/KeysetStep.kt b/src/test/java/com/pubnub/contract/step/KeysetStep.kt new file mode 100644 index 000000000..534bdae1a --- /dev/null +++ b/src/test/java/com/pubnub/contract/step/KeysetStep.kt @@ -0,0 +1,22 @@ +package com.pubnub.contract.step + +import com.pubnub.contract.CONTRACT_TEST_CONFIG +import com.pubnub.contract.state.World +import io.cucumber.java.en.Given +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +class KeysetStep(private val world: World) { + + @Given("I have a keyset with access manager enabled") + fun i_have_a_keyset_with_access_manager_enabled() { + MatcherAssert.assertThat(CONTRACT_TEST_CONFIG.pamPubKey(), Matchers.notNullValue()) + MatcherAssert.assertThat(CONTRACT_TEST_CONFIG.pamSubKey(), Matchers.notNullValue()) + MatcherAssert.assertThat(CONTRACT_TEST_CONFIG.pamSecKey(), Matchers.notNullValue()) + world.configuration.apply { + subscribeKey = CONTRACT_TEST_CONFIG.pamSubKey() + publishKey = CONTRACT_TEST_CONFIG.pamPubKey() + secretKey = CONTRACT_TEST_CONFIG.pamSecKey() + } + } +} \ No newline at end of file diff --git a/src/test/java/com/pubnub/contract/step/ThenSteps.kt b/src/test/java/com/pubnub/contract/step/ThenSteps.kt new file mode 100644 index 000000000..5cc9f3ea0 --- /dev/null +++ b/src/test/java/com/pubnub/contract/step/ThenSteps.kt @@ -0,0 +1,46 @@ +package com.pubnub.contract.step + +import com.pubnub.contract.access.state.GrantTokenState +import com.pubnub.contract.state.World +import io.cucumber.java.PendingException +import io.cucumber.java.en.Then +import org.junit.Assert.* + + +class ThenSteps(private val world: World) { + + @Then("an error is returned") + fun an_error_is_returned() { + assertNotNull(world.pnException) + } + + @Then("the error status code is {int}") + fun the_error_status_code_is(statusCode: Int) { + assertEquals(statusCode, world.pnException?.statusCode) + } + + @Then("the error message is {string}") + fun the_error_message_is(message: String) { + assertTrue("Exception ${world.pnException} should contain message $message", world.pnException?.message?.contains(message) ?: false) + } + + @Then("the error source is {string}") + fun the_error_source_is(source: String) { + assertTrue("Exception ${world.pnException} should contain source $source", world.pnException?.message?.contains(source) ?: false) + } + + @Then("the error detail message is {string}") + fun the_error_detail_message_is(details: String) { + assertTrue("Exception ${world.pnException} should contain error details $details", world.pnException?.message?.contains(details) ?: false) + } + + @Then("the error detail location is {string}") + fun the_error_detail_location_is(location: String) { + assertTrue("Exception ${world.pnException} should contain location $location", world.pnException?.message?.contains(location) ?: false) + } + + @Then("the error detail location type is {string}") + fun the_error_detail_location_type_is(locationType: String) { + assertTrue("Exception ${world.pnException} should contain locationType $locationType", world.pnException?.message?.contains(locationType) ?: false) + } +} \ No newline at end of file diff --git a/src/test/resources/entityTooLarge.xml b/src/test/resources/entityTooLarge.xml new file mode 100644 index 000000000..4621bdfb9 --- /dev/null +++ b/src/test/resources/entityTooLarge.xml @@ -0,0 +1,9 @@ + + + EntityTooLarge + Your proposed upload exceeds the maximum allowed size + 5282 + 5120 + ES1J0M4J8Z1K9R1T + hADJBPzd5nX3X6t/jS/0NwFChBR2qUG/APFr2S6cJURmS/0XEszOVkPJ2KEssTMoN1xCN2+Uqhk= + diff --git a/src/test/resources/fileEvent.json b/src/test/resources/fileEvent.json new file mode 100644 index 000000000..0affd9bf7 --- /dev/null +++ b/src/test/resources/fileEvent.json @@ -0,0 +1,29 @@ +{ + "t": { + "t": "16043981033884668", + "r": 12 + }, + "m": [ + { + "a": "2", + "f": 0, + "e": 4, + "i": "client-e1bbfc9a-967a-4f35-a4d6-b004c30acc14", + "p": { + "t": "16043981033865509", + "r": 12 + }, + "k": "sub-c-b9fe0b18-a646-11ea-ae1a-36d49400aaff", + "c": "ch_vmwmwuyadt", + "u": "This is meta", + "d": { + "message": "This is message", + "file": { + "id": "5a38cc2b-0416-437e-ab14-f8b431f8383f", + "name": "fileNamech_vmwmwuyadt.txt" + } + } + } + ] +} + diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 000000000..0cb42c4cd --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,18 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + \ No newline at end of file diff --git a/stale_outputs_checked b/stale_outputs_checked new file mode 100644 index 000000000..e69de29bb diff --git a/test.properties.example b/test.properties.example new file mode 100644 index 000000000..b3a894eda --- /dev/null +++ b/test.properties.example @@ -0,0 +1,11 @@ +# Substitute <> with value obtained from https://admin.pubnub.com/ +pubKey: <> +subKey: <> +pamPubKey: <> +pamSubKey: <> +pamSecKey: <> +featuresDir: <> +cucumberTags: <> +serverHostPort: localhost:8090 +#server.hostPort: ps.pndsn.com +serverMock: true \ No newline at end of file