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
+
+[](https://travis-ci.com/pubnub/java)
+[](https://www.codacy.com/app/PubNub/java?utm_source=github.com&utm_medium=referral&utm_content=pubnub/java&utm_campaign=Badge_Grade)
+[](https://www.codacy.com/app/PubNub/java?utm_source=github.com&utm_medium=referral&utm_content=pubnub/java&utm_campaign=Badge_Coverage)
+[](https://bintray.com/bintray/jcenter/com.pubnub%3Apubnub-gson/_latestVersion)
+[]()
+
+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