diff --git a/.gitignore b/.gitignore index cd968238..92715af9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,14 @@ *.class -.idea -*.iml - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # *.jar *.war *.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# Maven +.gradle/ +.idea/ +.syntastic_javac_config +build/ +gradle.properties +gradle/ +gradlew +gradlew.bat +out/ target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties diff --git a/.java-version b/.java-version new file mode 100644 index 00000000..ba6a626d --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +oracle64-10.0.2 diff --git a/.travis/publish.sh b/.travis/publish.sh deleted file mode 100755 index 6f5021e6..00000000 --- a/.travis/publish.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# -# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. -# -# Adapted from https://coderwall.com/p/9b_lfq and -# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ - -SLUG="GetStream/stream-java" -JDK="oraclejdk8" -BRANCH="master" - -set -e - -if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then - echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." -elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then - echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." -elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo "Skipping snapshot deployment: was pull request." -elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then - echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." -else - echo "Deploying snapshot..." - mvn clean deploy --settings=".travis/settings.xml" -Dmaven.test.skip=true - echo "Snapshot deployed!" -fi \ No newline at end of file diff --git a/.travis/settings.xml b/.travis/settings.xml deleted file mode 100644 index de371552..00000000 --- a/.travis/settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - ossrh - ${env.CI_DEPLOY_USERNAME} - ${env.CI_DEPLOY_PASSWORD} - - - \ No newline at end of file diff --git a/LICENSE b/LICENCE similarity index 59% rename from LICENSE rename to LICENCE index 8a25cc4d..c59b050e 100644 --- a/LICENSE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2017 Stream.io Inc, and individual contributors. +Copyright (c) 2016-2018 Stream.io Inc, and individual contributors. All rights reserved. @@ -24,25 +24,4 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -This software incorporates additional Open Source components. You can find the source code of these -open source projects along with license information below. - - Base64.java: - - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Base64.java - - Copyright (C) 2010 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 90694696..e49391e5 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,25 @@ stream-java =========== -[![Build Status](https://travis-ci.org/GetStream/stream-java.svg?branch=master)](https://travis-ci.org/GetStream/stream-java) - [stream-java](https://github.com/GetStream/stream-java) is a Java client for [Stream](https://getstream.io/). You can sign up for a Stream account at https://getstream.io/get_started. -The Stream's Java client come in two different flavours, you should decide which one to drag into your project. -Those two implementations differ according to the underlying library used to handle HTTP connections: - -- *stream-repo-apache* uses Apache HttpClient and we recommend it for backend applications. Apache HttpClient is a mature, reliable and rock-solid HTTP library. -- *stream-repo-okhttp* uses Square's OkHttp which is lightweight, powerful and mobile-oriented HTTP library. We recommend it for mobile application. - ### Installation -If you decide to go for the *Apache HttpClient* implementation, add the following dependency to your pom.xml: - -```xml - - io.getstream.client - stream-repo-apache - 2.1.3 - -``` - -or in your build.gradle: - -```gradle -compile 'io.getstream.client:stream-repo-apache:2.1.3' -``` - -Instead, if you opted for the *OkHttp* implementation please add it to your pom.xml +Add the following dependency to your pom.xml: ```xml io.getstream.client - stream-repo-okhttp - 2.1.3 + stream-java + 3.0.0 ``` or in your build.gradle: ```gradle -compile 'io.getstream.client:stream-repo-okhttp:2.1.3' +compile 'io.getstream.client:stream-java:3.0.0' ``` In case you want to download the artifact and put it manually into your project, @@ -55,127 +31,32 @@ Snapshots of the development version are available in [Sonatype](https://oss.son This API Client project requires Java SE 8. -See the [Travis configuration](.travis.yml) for details of how it is built, tested and packaged. - ### Full documentation Documentation for this Java client are available at the [Stream website](https://getstream.io/docs/?language=java). -### Usage - -```java -/** - * Instantiate a new client to connect to us east API endpoint - * Find your API keys here https://getstream.io/dashboard/ - **/ - -StreamClient streamClient = new StreamClientImpl(new ClientConfiguration(), "", ""); -``` - -#### Create a new Feed - -```java -/* Instantiate a feed object */ -Feed feed = streamClient.newFeed("user", "1"); -``` - -#### Working with Activities - -```java -/* Create an activity service */ -FlatActivityServiceImpl flatActivityService = feed.newFlatActivityService(SimpleActivity.class); - -/* Get activities from 5 to 10 (using offset pagination) */ -FeedFilter filter = new FeedFilter.Builder().withLimit(5).withOffset(5).build(); -List activities = flatActivityService.getActivities(filter).getResults(); - -/* Filter on an id less than the given UUID */ -aid = "e561de8f-00f1-11e4-b400-0cc47a024be0"; -FeedFilter filter = new FeedFilter.Builder().withIdLowerThan(aid).withLimit(5).build(); -List activities = flatActivityService.getActivities(filter).getResults(); +For examples have a look [here](https://github.com/GetStream/stream-java/tree/master/example/Example.java). -/* Create a new activity */ -SimpleActivity activity = new SimpleActivity(); -activity.setActor("user:1"); -activity.setObject("tweet:1"); -activity.setVerb("tweet"); -activity.setForeignId("tweet:1"); -SimpleActivity response = flatActivityService.addActivity(activity); - -/* Remove an activity by its id */ -feed.deleteActivity("e561de8f-00f1-11e4-b400-0cc47a024be0"); - -/* Remove activities by their foreign_id */ -feed.deleteActivityByForeignId("tweet:1"); -``` - -In case you want to add a single activity to multiple feeds, you can use the batch feature _addToMany_: - -```java -/* Batch adding activities to many feeds */ -flatActivityService.addActivityToMany(ImmutableList.of("user:1", "user:2").asList(), myActivity); -``` - -The API client allows you to send activities with custom field as well, you can find a -complete example [here](https://github.com/GetStream/stream-java/blob/master/stream-repo-apache/src/test/java/io/getstream/client/example/mixtype/MixedType.java) - -#### Follow and Unfollow - -```java -/* Follow another feed */ -feed.follow(flat", "42"); - -/* Stop following another feed */ -feed.unfollow(flat", "42"); - -/* Retrieve first 10 followers of a feed */ -FeedFilter filter = new FeedFilter.Builder().withLimit(10).build(); -List followingPaged = feed.getFollowing(filter); - -/* Retrieve the first 10 followed feeds */ -FeedFilter filter = new FeedFilter.Builder().withLimit(10).build(); -List followingPaged = feed.getFollowing(filter); -``` - -In case you want to send to Stream a long list of following relationships you can use the batch feature _followMany_: - -```java -/* Batch following many feeds */ -FollowMany followMany = new FollowMany.Builder() - .add("user:1", "user:2") - .add("user:1", "user:3") - .add("user:1", "user:4") - .add("user:2", "user:3") - .build(); -feed.followMany(followMany); - -``` - -#### Client token - -In order to generate a token for client side usage (e.g. JS client), you can use the following code: - -```java -/* Generating tokens for client side usage */ -String token = feed.getToken(); -``` +Docs are available on [GetStream.io](http://getstream.io/docs/). -#### Further references +Javadocs are available [here](https://getstream.github.io/stream-java/). -For more examples have a look [here](https://github.com/GetStream/stream-java/tree/master/stream-repo-apache/src/test/java/io/getstream/client/apache/example). +### Building & Testing -Docs are available on [GetStream.io](http://getstream.io/docs/). +Run `gradle wrapper --gradle-version 5.0` to generate gradle wrapper files -Javadocs are available [here](https://getstream.github.io/stream-java/). +Run `gradle test` to execute integration tests ### Credits & Contributors -This project was originally contributed by [Alessandro Pieri](sirio7g), prior to him joining Stream as an employee. +Project is maintained by [Max Klyga](nekuromento). + +This project was originally contributed by [Alessandro Pieri](sirio7g). We continue to welcome pull requests from community members. ### Copyright and License Information -Copyright (c) 2016-2017 Stream.io Inc, and individual contributors. All rights reserved. +Copyright (c) 2016-2018 Stream.io Inc, and individual contributors. All rights reserved. -See the file "LICENSE" for information on the history of this software, terms & conditions for usage, and a DISCLAIMER OF ALL WARRANTIES. +See the file "LICENSE" for information on the history of this software, terms & conditions for usage, and a DISCLAIMER OF ALL WARRANTIES. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..d3573920 --- /dev/null +++ b/build.gradle @@ -0,0 +1,127 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'signing' + id "com.scuilion.syntastic" version "0.3.8" + id "com.prot.versioninfo" version "0.5" +} + +group 'io.getstream.client' +version = '3.0.0' + +dependencies { + testCompile 'org.junit.jupiter:junit-jupiter-api:5.3.1' + testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.1' + testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.3.1' + testRuntime 'org.junit.vintage:junit-vintage-engine:5.3.1' + testCompile 'com.pholser:junit-quickcheck-core:0.8.1' + testCompile 'com.pholser:junit-quickcheck-generators:0.8.1' + + implementation 'com.google.guava:guava:26.0-jre' + + implementation 'com.squareup.okhttp3:okhttp:3.11.0' + + compile 'com.fasterxml.jackson.core:jackson-core:2.9.6' + compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.6' + compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6' + + compile 'com.auth0:java-jwt:3.4.0' +} + +compileJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} + +test { + useJUnitPlatform { + includeEngines 'junit-jupiter', 'junit-vintage' + } + testLogging { + exceptionFormat = 'full' + events 'standard_out', 'standard_error', "passed", "skipped", "failed" + } +} + +repositories { + jcenter() +} + +processResources { + filesMatching('stream-java2.info') { + expand(project.properties) + } +} + +task sourcesJar(type: Jar) { + from sourceSets.main.allJava + classifier = 'sources' +} + +task javadocJar(type: Jar) { + from javadoc + classifier = 'javadoc' +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId = 'stream-java' + from components.java + artifact sourcesJar + artifact javadocJar + + pom { + name = 'client' + description = 'Stream API official client' + url = 'https://github.com/GetStream/stream-java' + + scm { + url = 'scm:git@github.com:GetStream/stream-java.git' + connection = 'scm:git@github.com:GetStream/stream-java.git' + developerConnection = 'scm:git@github.com:GetStream/stream-java.git' + } + + licenses { + license { + name = 'The 3-Clause BSD License' + url = 'https://opensource.org/licenses/BSD-3-Clause' + distribution = 'repo' + } + } + + developers { + developer { + id = 'sirio7g' + name = 'Alessandro Pieri' + } + developer { + id = 'nekuromento' + name = 'Max Klyga' + } + } + } + } + } + repositories { + maven { + url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' + credentials { + username = sonatypeUsername + password = sonatypePassword + } + } + } +} + +signing { + sign publishing.publications.mavenJava +} + +javadoc { + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption('html5', true) + } +} diff --git a/data/test.jpg b/data/test.jpg new file mode 100644 index 00000000..fd87cca4 Binary files /dev/null and b/data/test.jpg differ diff --git a/data/test.txt b/data/test.txt new file mode 100644 index 00000000..f0c79c33 --- /dev/null +++ b/data/test.txt @@ -0,0 +1 @@ +Hello Stream! \ No newline at end of file diff --git a/example/Example.java b/example/Example.java new file mode 100644 index 00000000..c5795aca --- /dev/null +++ b/example/Example.java @@ -0,0 +1,628 @@ +package example; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import io.getstream.client.Client; +import io.getstream.client.FlatFeed; +import io.getstream.client.NotificationFeed; +import io.getstream.core.KeepHistory; +import io.getstream.core.LookupKind; +import io.getstream.core.Region; +import io.getstream.core.models.*; +import io.getstream.core.options.*; + +import java.io.File; +import java.net.URL; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static io.getstream.core.utils.Enrichment.createCollectionReference; +import static io.getstream.core.utils.Enrichment.createUserReference; + +class Example { + private static final String apiKey = "gp6e8sxxzud6"; + private static final String secret = "7j7exnksc4nxy399fdxvjqyqsqdahax3nfgtp27pumpc7sfm9um688pzpxjpjbf2"; + + public static void main(String[] args) throws Exception { + Client client = Client.builder(apiKey, secret) + .build(); + + FlatFeed chris = client.flatFeed("user", "chris"); + // Add an Activity; message is a custom field - tip: you can add unlimited custom fields! + chris.addActivity(Activity.builder() + .actor("chris") + .verb("add") + .object("picture:10") + .foreignID("picture:10") + .extraField("message", "Beautiful bird!") + .build()); + + // Create a following relationship between Jack's "timeline" feed and Chris' "user" feed: + FlatFeed jack = client.flatFeed("timeline", "jack"); + jack.follow(chris); + + // Read Jack's timeline and Chris' post appears in the feed: + List response = jack.getActivities(new Pagination().limit(10)).join(); + for (Activity activity : response) { + // ... + } + + // Remove an Activity by referencing it's foreign_id + chris.removeActivityByForeignID("picture:10"); + + /* -------------------------------------------------------- */ + + // Instantiate a feed object + FlatFeed userFeed = client.flatFeed("user", "1"); + + // Add an activity to the feed, where actor, object and target are references to objects (`Eric`, `Hawaii`, `Places to Visit`) + Activity activity = Activity.builder() + .actor("User:1") + .verb("pin") + .object("Place:42") + .target("Board:1") + .build(); + userFeed.addActivity(activity); + + // Create a bit more complex activity + activity = Activity.builder() + .actor("User:1") + .verb("run") + .object("Exercise:42") + .foreignID("run:1") + .extra(new ImmutableMap.Builder() + .put("course", new ImmutableMap.Builder() + .put("name", "Golden Gate park") + .put("distance", 10) + .build()) + .put("participants", new String[]{ + "Thierry", + "Tommaso", + }) + .put("started_at", LocalDateTime.now()) + .put("location", new ImmutableMap.Builder() + .put("type", "point") + .put("coordinates", new double[]{37.769722, -122.476944}) + .build()) + .build()) + .build(); + userFeed.addActivity(activity); + + // Remove an activity by its id + userFeed.removeActivityByID("e561de8f-00f1-11e4-b400-0cc47a024be0"); + + // Remove activities with foreign_id 'run:1' + userFeed.removeActivityByForeignID("run:1"); + + activity = Activity.builder() + .actor("1") + .verb("like") + .object("3") + .time(new Date()) + .foreignID("like:3") + .extraField("popularity", 100) + .build(); + + // first time the activity is added + userFeed.addActivity(activity); + + // update the popularity value for the activity + activity = Activity.builder() + .fromActivity(activity) + .extraField("popularity", 10) + .build(); + + client.batch().updateActivities(activity); + + /* -------------------------------------------------------- */ + + // partial update by activity ID + + // prepare the set operations + Map set = new ImmutableMap.Builder() + .put("product.price", 19.99) + .put("shares", new ImmutableMap.Builder() + .put("facebook", "...") + .put("twitter", "...") + .build()) + .build(); + // prepare the unset operations + String[] unset = new String[] { "daily_likes", "popularity" }; + + String id = "54a60c1e-4ee3-494b-a1e3-50c06acb5ed4"; + client.updateActivityByID(id, set, unset); + + String foreignID = "product:123"; + Date timestamp = new Date(); + client.updateActivityByForeignID(foreignID, timestamp, set, unset); + + FeedID[] add = new FeedID[0]; + FeedID[] remove = new FeedID[0]; + userFeed.updateActivityToTargets(activity, add, remove); + + FeedID[] newTargets = new FeedID[0]; + userFeed.replaceActivityToTargets(activity, newTargets); + + /* -------------------------------------------------------- */ + + Date now = new Date(); + Activity firstActivity = userFeed.addActivity(Activity.builder() + .actor("1") + .verb("like") + .object("3") + .time(now) + .foreignID("like:3") + .build()).join(); + Activity secondActivity = userFeed.addActivity(Activity.builder() + .actor("1") + .verb("like") + .object("3") + .time(now) + .extraField("extra", "extra_value") + .foreignID("like:3") + .build()).join(); + // foreign ID and time are the same for both activities + // hence only one activity is created and first and second IDs are equal + // firstActivity.ID == secondActivity.ID + + /* -------------------------------------------------------- */ + + // Get 5 activities with id less than the given UUID (Faster - Recommended!) + response = userFeed.getActivities(new Filter().idLessThan("e561de8f-00f1-11e4-b400-0cc47a024be0").limit(5)).join(); + // Get activities from 5 to 10 (Pagination-based - Slower) + response = userFeed.getActivities(new Pagination().offset(0).limit(5)).join(); + // Get activities sorted by rank (Ranked Feeds Enabled): + response = userFeed.getActivities(new Pagination().limit(5), "popularity").join(); + + /* -------------------------------------------------------- */ + + // timeline:timeline_feed_1 follows user:user_42 + FlatFeed user = client.flatFeed("user", "user_42"); + FlatFeed timeline = client.flatFeed("timeline", "timeline_feed_1"); + timeline.follow(user); + + // follow feed without copying the activities: + timeline.follow(user, 0); + + /* -------------------------------------------------------- */ + + // user := client.FlatFeed("user", "42") + + // Stop following feed user:user_42 + timeline.unfollow(user); + + // Stop following feed user:user_42 but keep history of activities + timeline.unfollow(user, KeepHistory.YES); + + // list followers + List followers = userFeed.getFollowers(new Pagination().offset(0).limit(10)).join(); + for (FollowRelation follow : followers) { + System.out.format("%s -> %s", follow.getSource(), follow.getTarget()); + // ... + } + + // Retrieve last 10 feeds followed by user_feed_1 + List followed = userFeed.getFollowed(new Pagination().offset(0).limit(10)).join(); + + // Retrieve 10 feeds followed by user_feed_1 starting from the 11th + followed = userFeed.getFollowed(new Pagination().offset(10).limit(10)).join(); + + // Check if user_feed_1 follows specific feeds + followed = userFeed.getFollowed(new Pagination().offset(0).limit(2), new FeedID("user:42"), new FeedID("user", "43")).join(); + + /* -------------------------------------------------------- */ + + NotificationFeed notifications = client.notificationFeed("notifications", "1"); + // Mark all activities in the feed as seen + List> activityGroups = notifications.getActivities(new ActivityMarker().allSeen()).join(); + for (NotificationGroup group : activityGroups) { + // ... + } + // Mark some activities as read via specific Activity Group Ids + activityGroups = notifications.getActivities(new ActivityMarker().read("groupID1", "groupID2" /* ... */)).join(); + + /* -------------------------------------------------------- */ + + // Add an activity to the feed, where actor, object and target are references to objects - adding your ranking method as a parameter (in this case, "popularity"): + activity = Activity.builder() + .actor("User:1") + .verb("pin") + .object("place:42") + .target("board:1") + .extraField("popularity", 5) + .build(); + userFeed.addActivity(activity); + + // Get activities sorted by the ranking method labelled 'activity_popularity' (Ranked Feeds Enabled) + response = userFeed.getActivities(new Pagination().limit(5), "activity_popularity").join(); + + /* -------------------------------------------------------- */ + + // Add the activity to Eric's feed and to Jessica's notification feed + activity = Activity.builder() + .actor("User:Eric") + .verb("tweet") + .object("tweet:id") + .to(Lists.newArrayList(new FeedID("notification:Jessica"))) + .extraField("message", "@Jessica check out getstream.io it's so dang awesome.") + .build(); + userFeed.addActivity(activity); + + // The TO field ensures the activity is send to the player, match and team feed + activity = Activity.builder() + .actor("Player:Suarez") + .verb("foul") + .object("Player:Ramos") + .to(Lists.newArrayList(new FeedID("team:barcelona"), new FeedID("match:1"))) + .extraField("match", ImmutableMap.of("El Classico", 10)) + .build(); + // playerFeed.addActivity(activity); + userFeed.addActivity(activity); + + /* -------------------------------------------------------- */ + + // Batch following many feeds + // Let timeline:1 will follow user:1, user:2 and user:3 + FollowRelation[] follows = new FollowRelation[]{ + new FollowRelation("timeline:1", "user:1"), + new FollowRelation("timeline:3", "user:2"), + new FollowRelation("timeline:1", "user:3") + }; + client.batch().followMany(follows); + // copy only the last 10 activities from every feed + client.batch().followMany(10, follows); + + /* -------------------------------------------------------- */ + + Activity[] activities = new Activity[]{ + Activity.builder() + .actor("User:1") + .verb("tweet") + .object("Tweet:1") + .build(), + Activity.builder() + .actor("User:2") + .verb("watch") + .object("Movie:1") + .build() + }; + userFeed.addActivities(activities); + + /* -------------------------------------------------------- */ + + // adds 1 activity to many feeds in one request + activity = Activity.builder() + .actor("User:2") + .verb("pin") + .object("Place:42") + .target("Board:1") + .build(); + FeedID[] feeds = new FeedID[]{ + new FeedID("timeline", "1"), + new FeedID("timeline", "2"), + new FeedID("timeline", "3"), + new FeedID("timeline", "4") + }; + client.batch().addToMany(activity, feeds); + + /* -------------------------------------------------------- */ + + // retrieve two activities by ID + client.batch().getActivitiesByID("01b3c1dd-e7ab-4649-b5b3-b4371d8f7045", "ed2837a6-0a3b-4679-adc1-778a1704852"); + + // retrieve an activity by foreign ID and time + client.batch().getActivitiesByForeignID(new ForeignIDTimePair("foreignID1", new Date()), new ForeignIDTimePair("foreignID2", new Date())); + + /* -------------------------------------------------------- */ + + // connect to the us-east region + client = Client.builder(apiKey, secret) + .region(Region.US_EAST) + .build(); + + /* -------------------------------------------------------- */ + Reaction like = new Reaction.Builder() + .kind("like") + .activityID(activity.getID()) + .build(); + + // add a like reaction to the activity with id activityId + like = client.reactions().add("john-doe", like).join(); + + Reaction comment = new Reaction.Builder() + .kind("comment") + .activityID(activity.getID()) + .extraField("text", "awesome post!") + .build(); + + // adds a comment reaction to the activity with id activityId + comment = client.reactions().add("john-doe", comment).join(); + + /* -------------------------------------------------------- */ + + // first let's read current user's timeline feed and pick one activity + response = client.flatFeed("timeline", "mike").getActivities().join(); + activity = response.get(0); + + // then let's add a like reaction to that activity + client.reactions().add("john-doe", Reaction.builder() + .kind("like") + .activityID(activity.getID()) + .build()); + + /* -------------------------------------------------------- */ + + comment = new Reaction.Builder() + .kind("comment") + .activityID(activity.getID()) + .extraField("text", "awesome post!") + .build(); + + // adds a comment reaction to the activity and notify Thierry's notification feed + client.reactions().add("john-doe", comment, new FeedID("notification:thierry")); + + /* -------------------------------------------------------- */ + + // read bob's timeline and include most recent reactions to all activities and their total count + client.flatFeed("timeline", "bob") + .getEnrichedActivities(new EnrichmentFlags() + .withRecentReactions() + .withReactionCounts()); + + // read bob's timeline and include most recent reactions to all activities and her own reactions + client.flatFeed("timeline", "bob") + .getEnrichedActivities(new EnrichmentFlags() + .withOwnReactions() + .withRecentReactions() + .withReactionCounts()); + + /* -------------------------------------------------------- */ + + // retrieve all kind of reactions for an activity + List reactions = client.reactions().filter(LookupKind.ACTIVITY, "ed2837a6-0a3b-4679-adc1-778a1704852d").join(); + + // retrieve first 10 likes for an activity + reactions = client.reactions() + .filter(LookupKind.ACTIVITY, + "ed2837a6-0a3b-4679-adc1-778a1704852d", + new Filter().limit(10), + "like").join(); + + // retrieve the next 10 likes using the id_lt param + reactions = client.reactions() + .filter(LookupKind.ACTIVITY, + "ed2837a6-0a3b-4679-adc1-778a1704852d", + new Filter().idLessThan("e561de8f-00f1-11e4-b400-0cc47a024be0"), + "like").join(); + + /* -------------------------------------------------------- */ + + // adds a like to the previously created comment + Reaction reaction = client.reactions().addChild("john-doe", comment.getId(), Reaction.builder().kind("like").build()).join(); + + /* -------------------------------------------------------- */ + + client.reactions().update(Reaction.builder() + .id(reaction.getId()) + .extraField("text", "love it!") + .build()); + + /* -------------------------------------------------------- */ + + client.reactions().delete(reaction.getId()); + + /* -------------------------------------------------------- */ + + client.collections().add("food", new CollectionData("cheese-burger") + .set("name", "Cheese Burger") + .set("rating", "4 stars")); + + // if you don't have an id on your side, just use null as the ID and Stream will generate a unique ID + client.collections().add("food", new CollectionData() + .set("name", "Cheese Burger") + .set("rating", "4 stars")); + + /* -------------------------------------------------------- */ + + CollectionData collection = client.collections().get("food", "cheese-burger").join(); + + /* -------------------------------------------------------- */ + + client.collections().delete("food", "cheese-burger"); + + /* -------------------------------------------------------- */ + + client.collections().update("food", new CollectionData("cheese-burger") + .set("name", "Cheese Burger") + .set("rating", "1 star")); + + /* -------------------------------------------------------- */ + + client.collections().upsert("visitor", + new CollectionData("123") + .set("name", "John") + .set("favorite_color", "blue"), + new CollectionData("124") + .set("name", "Jane") + .set("favorite_color", "purple") + .set("interests", Lists.newArrayList("fashion", "jazz"))); + + /* -------------------------------------------------------- */ + + // select the entries with ID 123 and 124 from items collection + List objects = client.collections().select("items", "123", "124").join(); + + /* -------------------------------------------------------- */ + + // delete the entries with ID 123 and 124 from visitor collection + client.collections().deleteMany("visitor", "123", "124"); + + /* -------------------------------------------------------- */ + + // first we add our object to the food collection + CollectionData cheeseBurger = client.collections().add("food", new CollectionData("123") + .set("name", "Cheese Burger") + .set("ingredients", Lists.newArrayList("cheese", "burger", "bread", "lettuce", "tomato"))).join(); + + // the object returned by .add can be embedded directly inside of an activity + userFeed.addActivity(Activity.builder() + .actor(createUserReference("john-doe")) + .verb("grill") + .object(createCollectionReference(cheeseBurger.getCollection(), cheeseBurger.getID())) + .build()); + + // if we now read the feed, the activity we just added will include the entire full object + userFeed.getEnrichedActivities(); + + // we can then update the object and Stream will propagate the change to all activities + client.collections().update(cheeseBurger.getCollection(), cheeseBurger + .set("name", "Amazing Cheese Burger") + .set("ingredients", Lists.newArrayList("cheese", "burger", "bread", "lettuce", "tomato"))).join(); + + /* -------------------------------------------------------- */ + + // First create a collection entry with upsert api + client.collections().upsert("food", new CollectionData().set("name", "Cheese Burger")); + + // Then create a user + client.user("john-doe").create(new Data() + .set("name", "John Doe") + .set("occupation", "Software Engineer") + .set("gender", "male")); + + // Since we know their IDs we can create references to both without reading from APIs + String cheeseBurgerRef = createCollectionReference("food", "cheese-burger"); + String johnDoeRef = createUserReference("john-doe"); + + client.flatFeed("user", "john").addActivity(Activity.builder() + .actor(johnDoeRef) + .verb("eat") + .object(cheeseBurgerRef) + .build()); + + /* -------------------------------------------------------- */ + + // create a new user, if the user already exist an error is returned + client.user("john-doe").create(new Data() + .set("name", "John Doe") + .set("occupation", "Software Engineer") + .set("gender", "male")); + + // get or create a new user, if the user already exist the user is returned + client.user("john-doe").getOrCreate(new Data() + .set("name", "John Doe") + .set("occupation", "Software Engineer") + .set("gender", "male")); + + /* -------------------------------------------------------- */ + + client.user("123").get(); + + /* -------------------------------------------------------- */ + + client.user("123").delete(); + + /* -------------------------------------------------------- */ + + client.user("123").update(new Data() + .set("name", "Jane Doe") + .set("occupation", "Software Engineer") + .set("gender", "female")); + + /* -------------------------------------------------------- */ + + // Read the personalization feed for a given user + client.personalization().get("personalized_feed", new ImmutableMap.Builder() + .put("user_id", 123) + .put("feed_slug", "timeline") + .build()); + + // Our data science team will typically tell you which endpoint to use + client.personalization().get("discovery_feed", new ImmutableMap.Builder() + .put("user_id", 123) + .put("source_feed_slug", "timeline") + .put("target_feed_slug", "user") + .build()); + + /* -------------------------------------------------------- */ + + client.analytics().trackEngagement(Engagement.builder() + .feedID("user:thierry") + .content(new Content("message:34349698") + .set("verb", "share") + .set("actor", ImmutableMap.of("1", "user1"))) + .boost(2) + .location("profile_page") + .position(3) + .build()); + + /* -------------------------------------------------------- */ + + client.analytics().trackImpression(Impression.builder() + .contentList(new Content("tweet:34349698") + .set("verb", "share") + .set("actor", ImmutableMap.of("1", "user1")), + new Content("tweet:34349699"), + new Content("tweet:34349700")) + .feedID("flat:tommaso") + .location("android-app") + .build()); + + /* -------------------------------------------------------- */ + + // the URL to direct to + URL targetURL = new URL("http://mysite.com/detail"); + + // track the impressions and a click + List impressions = Lists.newArrayList(Impression.builder() + .contentList(new Content("tweet:1"), + new Content("tweet:2"), + new Content("tweet:3")) + .userData(new UserData("tommaso", null)) + .location("email") + .feedID("user:global") + .build()); + List engagements = Lists.newArrayList(Engagement.builder() + .content(new Content("tweet:2")) + .label("click") + .position(1) + .userData(new UserData("tommaso", null)) + .location("email") + .feedID("user:global") + .build()); + + // when the user opens the tracking URL in their browser gets redirected to the target URL + // the events are added to our analytics platform + URL trackingURL = client.analytics().createRedirectURL(targetURL, impressions, engagements); + + /* -------------------------------------------------------- */ + + File image = new File("..."); + URL imageURL = client.images().upload(image).join(); + + File file = new File("..."); + URL fileURL = client.files().upload(file).join(); + + /* -------------------------------------------------------- */ + + // deleting an image using the url returned by the APIs + client.images().delete(imageURL); + + // deleting a file using the url returned by the APIs + client.files().delete(fileURL); + + /* -------------------------------------------------------- */ + + // create a 50x50 thumbnail and crop from center + client.images().process(imageURL, new Resize(50, 50, Resize.Type.CROP)); + + // create a 50x50 thumbnail using clipping (keeps aspect ratio) + client.images().process(imageURL, new Resize(50, 50, Resize.Type.CLIP)); + + /* -------------------------------------------------------- */ + + OGData urlPreview = client.openGraph(new URL("http://www.imdb.com/title/tt0117500/")).join(); + } +} diff --git a/pom.xml b/pom.xml deleted file mode 100644 index dbf15999..00000000 --- a/pom.xml +++ /dev/null @@ -1,331 +0,0 @@ - - - 4.0.0 - - io.getstream.client - stream-java - pom - 2.1.4-SNAPSHOT - - stream-java - stream-java is a Java client for http://getstream.io. - https://github.com/GetStream/stream-java - - - - The 3-Clause BSD License - https://opensource.org/licenses/BSD-3-Clause - - - Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - - sirio7g - Alessandro Pieri - https://github.com/sirio7g - - - tbarbugli - Tommaso Barbugli - https://github.com/tbarbugli - - - - - scm:git:git@github.com:GetStream/stream-java.git - scm:git:git@github.com:GetStream/stream-java.git - git@github.com:GetStream/stream-java.git - HEAD - - - - stream-core - stream-repo-apache - stream-repo-okhttp - - - - UTF-8 - UTF-8 - 1.6.6 - 1.1.2 - 18.0 - 2.4.3 - 1.0 - 4.11 - 1.10.19 - 3.3.0 - 1.57 - - true - false - - - - - - org.slf4j - slf4j-api - ${slf4j-api.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - com.google.guava - guava - ${guava.version} - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - org.tomitribe - tomitribe-http-signatures - ${tomitribe-http-signatures.version} - - - com.auth0 - java-jwt - ${java-jwt.version} - - - - - junit - junit - ${junit.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - - - com.github.tomakehurst - wiremock - ${wiremock.version} - test - - - standalone - - - org.mortbay.jetty - jetty - - - com.google.guava - guava - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - - - org.apache.httpcomponents - httpclient - - - org.skyscreamer - jsonassert - - - xmlunit - xmlunit - - - com.jayway.jsonpath - json-path - - - net.sf.jopt-simple - jopt-simple - - - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5 - - false - release - deploy - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.3 - - - aggregate - - aggregate - - site - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - true - - - - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - maven-release-plugin - 2.5 - - - org.apache.maven.plugins - maven-surefire-plugin - 2.19.1 - - ${skipTests} - - **/*IntegrationTest.java - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.19.1 - - ${skipTests} - ${skipITs} - - **/IntegrationTest.java - - - - - integration-test - - integration-test - verify - - - - - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..4f791be5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at https://docs.gradle.org/4.10.2/userguide/multi_project_builds.html + */ + +rootProject.name = 'stream-java' diff --git a/src/main/java/io/getstream/client/AggregatedFeed.java b/src/main/java/io/getstream/client/AggregatedFeed.java new file mode 100644 index 00000000..0d653617 --- /dev/null +++ b/src/main/java/io/getstream/client/AggregatedFeed.java @@ -0,0 +1,217 @@ +package io.getstream.client; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Group; +import io.getstream.core.options.ActivityMarker; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Pagination; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public class AggregatedFeed extends Feed { + AggregatedFeed(Client client, FeedID id) { + super(client, id); + } + + public CompletableFuture>> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + public CompletableFuture>> getActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getActivities(Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedActivities(Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/AnalyticsClient.java b/src/main/java/io/getstream/client/AnalyticsClient.java new file mode 100644 index 00000000..742d47d7 --- /dev/null +++ b/src/main/java/io/getstream/client/AnalyticsClient.java @@ -0,0 +1,56 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamAnalytics; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; +import io.getstream.core.utils.Auth.TokenAction; + +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import static io.getstream.core.utils.Auth.buildAnalyticsRedirectToken; +import static io.getstream.core.utils.Auth.buildAnalyticsToken; + +public final class AnalyticsClient { + private final String secret; + private final StreamAnalytics analytics; + + AnalyticsClient(String secret, StreamAnalytics analytics) { + this.secret = secret; + this.analytics = analytics; + } + + public CompletableFuture trackEngagement(Iterable events) throws StreamException { + return trackEngagement(Iterables.toArray(events, Engagement.class)); + } + + public CompletableFuture trackEngagement(Engagement... events) throws StreamException { + final Token token = buildAnalyticsToken(secret, TokenAction.WRITE); + return analytics.trackEngagement(token, events); + } + + public CompletableFuture trackImpression(Impression event) throws StreamException { + final Token token = buildAnalyticsToken(secret, TokenAction.WRITE); + return analytics.trackImpression(token, event); + } + + public URL createRedirectURL(URL url, Engagement... engagements) throws StreamException { + return createRedirectURL(url, new Impression[0], engagements); + } + + public URL createRedirectURL(URL url, Impression... impressions) throws StreamException { + return createRedirectURL(url, impressions, new Engagement[0]); + } + + public URL createRedirectURL(URL url, Iterable impressions, Iterable engagements) throws StreamException { + return createRedirectURL(url, Iterables.toArray(impressions, Impression.class), Iterables.toArray(engagements, Engagement.class)); + } + + public URL createRedirectURL(URL url, Impression[] impressions, Engagement[] engagements) throws StreamException { + final Token token = buildAnalyticsRedirectToken(secret); + return analytics.createRedirectURL(token, url, impressions, engagements); + } +} diff --git a/src/main/java/io/getstream/client/BatchClient.java b/src/main/java/io/getstream/client/BatchClient.java new file mode 100644 index 00000000..7cb05333 --- /dev/null +++ b/src/main/java/io/getstream/client/BatchClient.java @@ -0,0 +1,80 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamBatch; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.FollowRelation; +import io.getstream.core.models.ForeignIDTimePair; +import io.getstream.core.utils.DefaultOptions; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static io.getstream.core.utils.Auth.*; + +public final class BatchClient { + private final String secret; + private final StreamBatch batch; + + BatchClient(String secret, StreamBatch batch) { + this.secret = secret; + this.batch = batch; + } + + public CompletableFuture addToMany(Activity activity, FeedID... feeds) throws StreamException { + final Token token = buildFeedToken(secret, TokenAction.WRITE); + return batch.addToMany(token, activity, feeds); + } + + public CompletableFuture followMany(int activityCopyLimit, FollowRelation... follows) throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.WRITE); + return batch.followMany(token, activityCopyLimit, follows); + } + + public CompletableFuture followMany(int activityCopyLimit, Iterable follows) throws StreamException { + return followMany(activityCopyLimit, Iterables.toArray(follows, FollowRelation.class)); + } + + public CompletableFuture followMany(FollowRelation... follows) throws StreamException { + return followMany(DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT, follows); + } + + public CompletableFuture followMany(Iterable follows) throws StreamException { + return followMany(Iterables.toArray(follows, FollowRelation.class)); + } + + public CompletableFuture unfollowMany(FollowRelation... follows) throws StreamException { + final Token token = buildFollowToken(secret, TokenAction.DELETE); + return batch.unfollowMany(token, follows); + } + + public CompletableFuture> getActivitiesByID(Iterable activityIDs) throws StreamException { + return getActivitiesByID(Iterables.toArray(activityIDs, String.class)); + } + + public CompletableFuture> getActivitiesByID(String... activityIDs) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getActivitiesByID(token, activityIDs); + } + + public CompletableFuture> getActivitiesByForeignID(Iterable activityIDTimePairs) throws StreamException { + return getActivitiesByForeignID(Iterables.toArray(activityIDTimePairs, ForeignIDTimePair.class)); + } + + public CompletableFuture> getActivitiesByForeignID(ForeignIDTimePair... activityIDTimePairs) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.READ); + return batch.getActivitiesByForeignID(token, activityIDTimePairs); + } + + public CompletableFuture updateActivities(Iterable activities) throws StreamException { + return updateActivities(Iterables.toArray(activities, Activity.class)); + } + + public CompletableFuture updateActivities(Activity... activities) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return batch.updateActivities(token, activities); + } +} diff --git a/src/main/java/io/getstream/client/Client.java b/src/main/java/io/getstream/client/Client.java new file mode 100644 index 00000000..77d41a1e --- /dev/null +++ b/src/main/java/io/getstream/client/Client.java @@ -0,0 +1,296 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import io.getstream.core.Region; +import io.getstream.core.Stream; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.OKHTTPClientAdapter; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.*; +import io.getstream.core.options.RequestOption; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Auth.*; + +public final class Client { + private final String secret; + private final Stream stream; + + private Client(String key, String secret, URL baseURL, HTTPClient httpClient) { + this.secret = secret; + this.stream = new Stream(key, baseURL, httpClient); + } + + public static Builder builder(String apiKey, String secret) { + return new Builder(apiKey, secret); + } + + public CompletableFuture updateActivityByID(String id, Map set, Iterable unset) throws StreamException { + return updateActivityByID(id, set, Iterables.toArray(unset, String.class)); + } + + public CompletableFuture updateActivityByID(String id, Map set, String[] unset) throws StreamException { + final Token token = buildFeedToken(secret, TokenAction.WRITE); + return stream.updateActivityByID(token, id, set, unset); + } + + public CompletableFuture updateActivityByForeignID(ForeignIDTimePair foreignIDTimePair, Map set, Iterable unset) throws StreamException { + checkNotNull(foreignIDTimePair, "No activity to update"); + return updateActivityByForeignID(foreignIDTimePair.getForeignID(), foreignIDTimePair.getTime(), set, unset); + } + + public CompletableFuture updateActivityByForeignID(ForeignIDTimePair foreignIDTimePair, Map set, String[] unset) throws StreamException { + checkNotNull(foreignIDTimePair, "No activity to update"); + return updateActivityByForeignID(foreignIDTimePair.getForeignID(), foreignIDTimePair.getTime(), set, unset); + } + + public CompletableFuture updateActivityByForeignID(String foreignID, Date timestamp, Map set, Iterable unset) throws StreamException { + return updateActivityByForeignID(foreignID, timestamp, set, Iterables.toArray(unset, String.class)); + } + + public CompletableFuture updateActivityByForeignID(String foreignID, Date timestamp, Map set, String[] unset) throws StreamException { + final Token token = buildActivityToken(secret, TokenAction.WRITE); + return stream.updateActivityByForeignID(token, foreignID, timestamp, set, unset); + } + + public CompletableFuture openGraph(URL url) throws StreamException { + final Token token = buildOpenGraphToken(secret); + return stream.openGraph(token, url); + } + + public static final class Builder { + private static final String DEFAULT_HOST = "stream-io-api.com"; + + private final String apiKey; + private final String secret; + private HTTPClient httpClient; + + private String scheme = "https"; + private String region = Region.US_EAST.toString(); + private String host = DEFAULT_HOST; + private int port = 443; + + public Builder(String apiKey, String secret) { + checkNotNull(apiKey, "API key can't be null"); + checkNotNull(secret, "Secret can't be null"); + checkArgument(!apiKey.isEmpty(), "API key can't be empty"); + checkArgument(!secret.isEmpty(), "Secret can't be empty"); + this.apiKey = apiKey; + this.secret = secret; + } + + public Builder httpClient(HTTPClient httpClient) { + checkNotNull(httpClient, "HTTP client can't be null"); + this.httpClient = httpClient; + return this; + } + + public Builder scheme(String scheme) { + checkNotNull(scheme, "Scheme can't be null"); + checkArgument(!scheme.isEmpty(), "Scheme can't be empty"); + this.scheme = scheme; + return this; + } + + public Builder host(String host) { + checkNotNull(host, "Host can't be null"); + checkArgument(!host.isEmpty(), "Host can't be empty"); + this.host = host; + return this; + } + + public Builder port(int port) { + checkArgument(port > 0, "Port has to be a non-zero positive number"); + this.port = port; + return this; + } + + public Builder region(Region region) { + checkNotNull(region, "Region can't be null"); + this.region = region.toString(); + return this; + } + + public Builder region(String region) { + checkNotNull(region, "Region can't be null"); + checkArgument(!region.isEmpty(), "Region can't be empty"); + this.region = region; + return this; + } + + private String buildHost() { + final StringBuilder sb = new StringBuilder(); + if (host.equals(DEFAULT_HOST)) { + sb.append(region).append("."); + } + sb.append(host); + return sb.toString(); + } + + public Client build() throws MalformedURLException { + if (httpClient == null) { + httpClient = new OKHTTPClientAdapter(); + } + return new Client(apiKey, secret, new URL(scheme, buildHost(), port, ""), httpClient); + } + } + + public T getHTTPClientImplementation() { + return stream.getHTTPClientImplementation(); + } + + public Token frontendToken(String userID) { + return buildFrontendToken(secret, userID); + } + + public FlatFeed flatFeed(FeedID id) { + return new FlatFeed(this, id); + } + + public FlatFeed flatFeed(String slug, String userID) { + return flatFeed(new FeedID(slug, userID)); + } + + public AggregatedFeed aggregatedFeed(FeedID id) { + return new AggregatedFeed(this, id); + } + + public AggregatedFeed aggregatedFeed(String slug, String userID) { + return aggregatedFeed(new FeedID(slug, userID)); + } + + public NotificationFeed notificationFeed(FeedID id) { + return new NotificationFeed(this, id); + } + + public NotificationFeed notificationFeed(String slug, String userID) { + return notificationFeed(new FeedID(slug, userID)); + } + + public User user(String userID) { + return new User(this, userID); + } + + public BatchClient batch() { + return new BatchClient(secret, stream.batch()); + } + + public CollectionsClient collections() { + return new CollectionsClient(secret, stream.collections()); + } + + public PersonalizationClient personalization() { + return new PersonalizationClient(secret, stream.personalization()); + } + + public AnalyticsClient analytics() { + return new AnalyticsClient(secret, stream.analytics()); + } + + public ReactionsClient reactions() { + return new ReactionsClient(secret, stream.reactions()); + } + + public FileStorageClient files() { + return new FileStorageClient(secret, stream.files()); + } + + public ImageStorageClient images() { + return new ImageStorageClient(secret, stream.images()); + } + + CompletableFuture getActivities(FeedID feed, RequestOption... options) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.READ); + return stream.getActivities(token, feed, options); + } + + CompletableFuture getEnrichedActivities(FeedID feed, RequestOption... options) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.READ); + return stream.getEnrichedActivities(token, feed, options); + } + + CompletableFuture addActivity(FeedID feed, Activity activity) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.WRITE); + return stream.addActivity(token, feed, activity); + } + + CompletableFuture addActivities(FeedID feed, Activity... activities) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.WRITE); + return stream.addActivities(token, feed, activities); + } + + CompletableFuture removeActivityByID(FeedID feed, String id) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.DELETE); + return stream.removeActivityByID(token, feed, id); + } + + CompletableFuture removeActivityByForeignID(FeedID feed, String foreignID) throws StreamException { + final Token token = buildFeedToken(secret, feed, TokenAction.DELETE); + return stream.removeActivityByForeignID(token, feed, foreignID); + } + + CompletableFuture follow(FeedID source, FeedID target, int activityCopyLimit) throws StreamException { + final Token token = buildFollowToken(secret, source, TokenAction.WRITE); + final Token targetToken = buildFeedToken(secret, target, TokenAction.READ); + return stream.follow(token, targetToken, source, target, activityCopyLimit); + } + + CompletableFuture getFollowers(FeedID feed, RequestOption... options) throws StreamException { + final Token token = buildFollowToken(secret, feed, TokenAction.READ); + return stream.getFollowers(token, feed, options); + } + + CompletableFuture getFollowed(FeedID feed, RequestOption... options) throws StreamException { + final Token token = buildFollowToken(secret, feed, TokenAction.READ); + return stream.getFollowed(token, feed, options); + } + + CompletableFuture unfollow(FeedID source, FeedID target, RequestOption... options) throws StreamException { + final Token token = buildFollowToken(secret, source, TokenAction.DELETE); + return stream.unfollow(token, source, target, options); + } + + CompletableFuture updateActivityToTargets(FeedID feed, Activity activity, FeedID[] add, FeedID[] remove, FeedID[] newTargets) throws StreamException { + final Token token = buildToTargetUpdateToken(secret, feed, TokenAction.WRITE); + return stream.updateActivityToTargets(token, feed, activity, add, remove, newTargets); + } + + CompletableFuture getUser(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.READ); + return stream.getUser(token, id, false); + } + + CompletableFuture deleteUser(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.DELETE); + return stream.deleteUser(token, id); + } + + CompletableFuture getOrCreateUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.createUser(token, id, data, true); + } + + CompletableFuture createUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.createUser(token, id, data, false); + } + + CompletableFuture updateUser(String id, Data data) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.WRITE); + return stream.updateUser(token, id, data); + } + + CompletableFuture userProfile(String id) throws StreamException { + final Token token = buildUsersToken(secret, TokenAction.READ); + return stream.getUser(token, id, true); + } +} diff --git a/src/main/java/io/getstream/client/CollectionsClient.java b/src/main/java/io/getstream/client/CollectionsClient.java new file mode 100644 index 00000000..aac154e9 --- /dev/null +++ b/src/main/java/io/getstream/client/CollectionsClient.java @@ -0,0 +1,139 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; +import io.getstream.core.StreamCollections; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; +import io.getstream.core.utils.Auth.TokenAction; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static io.getstream.core.utils.Auth.buildCollectionsToken; +import static io.getstream.core.utils.Serialization.convert; + +public final class CollectionsClient { + private final String secret; + private final StreamCollections collections; + + CollectionsClient(String secret, StreamCollections collections) { + this.secret = secret; + this.collections = collections; + } + + public CompletableFuture addCustom(String collection, T item) throws StreamException { + return addCustom(null, collection, item); + } + + public CompletableFuture addCustom(String userID, String collection, T item) throws StreamException { + return add(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture add(String collection, CollectionData item) throws StreamException { + return add(null, collection, item); + } + + public CompletableFuture add(String userID, String collection, CollectionData item) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.add(token, userID, collection, item); + } + + public CompletableFuture updateCustom(String collection, T item) throws StreamException { + return updateCustom(null, collection, item); + } + + public CompletableFuture updateCustom(String userID, String collection, T item) throws StreamException { + return update(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture update(String collection, CollectionData item) throws StreamException { + return update(null, collection, item); + } + + public CompletableFuture update(String userID, String collection, CollectionData item) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.update(token, userID, collection, item); + } + + public CompletableFuture upsertCustom(String collection, Iterable items) throws StreamException { + final CollectionData[] custom = Streams.stream(items) + .map(item -> CollectionData.buildFrom(item)) + .toArray(CollectionData[]::new); + return upsert(collection, custom); + } + + public CompletableFuture upsertCustom(String collection, T... items) throws StreamException { + final CollectionData[] custom = Arrays.stream(items) + .map(item -> CollectionData.buildFrom(item)) + .toArray(CollectionData[]::new); + return upsert(collection, custom); + } + + public CompletableFuture upsert(String collection, Iterable items) throws StreamException { + return upsert(collection, Iterables.toArray(items, CollectionData.class)); + } + + public CompletableFuture upsert(String collection, CollectionData... items) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.WRITE); + return collections.upsert(token, collection, items); + } + + public CompletableFuture> customItems(Class type, String collection) throws StreamException { + return items(collection) + .thenApply(result -> result.stream() + .map(item -> convert(item, type)) + .collect(Collectors.toList())); + } + + public CompletableFuture> items(String collection) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.READ); + return collections.items(token, collection); + } + + public CompletableFuture getCustom(Class type, String collection, String id) throws StreamException { + return get(collection, id).thenApply(data -> convert(data, type)); + } + + public CompletableFuture get(String collection, String id) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.READ); + return collections.get(token, collection, id); + } + + public CompletableFuture> selectCustom(Class type, String collection, Iterable ids) throws StreamException { + return selectCustom(type, collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture> selectCustom(Class type, String collection, String... ids) throws StreamException { + return select(collection, ids) + .thenApply(data -> data.stream().map(item -> convert(item, type)).collect(Collectors.toList())); + } + + public CompletableFuture> select(String collection, Iterable ids) throws StreamException { + return select(collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture> select(String collection, String... ids) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.READ); + return collections.select(token, collection, ids); + } + + public CompletableFuture delete(String collection, String id) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.DELETE); + return collections.delete(token, collection, id); + } + + public CompletableFuture deleteMany(String collection, Iterable ids) throws StreamException { + return deleteMany(collection, Iterables.toArray(ids, String.class)); + } + + public CompletableFuture deleteMany(String collection, String... ids) throws StreamException { + final Token token = buildCollectionsToken(secret, TokenAction.DELETE); + return collections.deleteMany(token, collection, ids); + } +} diff --git a/src/main/java/io/getstream/client/Feed.java b/src/main/java/io/getstream/client/Feed.java new file mode 100644 index 00000000..3ab25599 --- /dev/null +++ b/src/main/java/io/getstream/client/Feed.java @@ -0,0 +1,278 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.FollowRelation; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Pagination; +import io.getstream.core.options.RequestOption; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.*; + +public class Feed { + private final Client client; + private final FeedID id; + + Feed(Client client, FeedID id) { + checkNotNull(client, "Can't create feed w/o a client"); + checkNotNull(id, "Can't create feed w/o an ID"); + + this.client = client; + this.id = id; + } + + protected final Client getClient() { + return client; + } + + public final FeedID getID() { + return id; + } + + public final String getSlug() { + return id.getSlug(); + } + + public final String getUserID() { + return id.getUserID(); + } + + public final CompletableFuture addActivity(Activity activity) throws StreamException { + return getClient() + .addActivity(id, activity) + .thenApply(response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture addCustomActivity(T activity) throws StreamException { + return getClient() + .addActivity(id, Activity.builder().fromCustomActivity(activity).build()) + .thenApply(response -> { + try { + return deserialize(response, (Class) activity.getClass()); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Iterable activities) throws StreamException { + return addActivities(Iterables.toArray(activities, Activity.class)); + } + + public final CompletableFuture> addCustomActivities(Iterable activities) throws StreamException { + final Activity[] custom = Streams.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply(response -> { + try { + Class element = (Class) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Activity... activities) throws StreamException { + return getClient() + .addActivities(id, activities) + .thenApply(response -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addCustomActivities(T... activities) throws StreamException { + final Activity[] custom = Arrays.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply(response -> { + try { + Class element = (Class) activities.getClass().getComponentType(); + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByID(String id) throws StreamException { + return client + .removeActivityByID(this.id, id) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByForeignID(String foreignID) throws StreamException { + return client + .removeActivityByForeignID(id, foreignID) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture follow(FlatFeed feed) throws StreamException { + return follow(feed, DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT); + } + + public final CompletableFuture follow(FlatFeed feed, int activityCopyLimit) throws StreamException { + checkArgument(activityCopyLimit <= DefaultOptions.MAX_ACTIVITY_COPY_LIMIT, String.format("Activity copy limit should be less then %d", DefaultOptions.MAX_ACTIVITY_COPY_LIMIT)); + + return client + .follow(id, feed.getID(), activityCopyLimit) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowers(Iterable feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_PAGINATION, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(FeedID... feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_PAGINATION, feedIDs); + } + + public final CompletableFuture> getFollowers(Pagination pagination, Iterable feedIDs) throws StreamException { + return getFollowers(pagination, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(Pagination pagination, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = Arrays.stream(feeds) + .map(id -> id.toString()) + .toArray(String[]::new); + final RequestOption[] options = feedIDs.length == 0 + ? new RequestOption[] { pagination } + : new RequestOption[] { pagination, new CustomQueryParameter("filter", String.join(",", feedIDs)) }; + return client + .getFollowers(id, options) + .thenApply(response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowed(Iterable feedIDs) throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_PAGINATION, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(FeedID... feedIDs) throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_PAGINATION, feedIDs); + } + + public final CompletableFuture> getFollowed(Pagination pagination, Iterable feedIDs) throws StreamException { + return getFollowed(pagination, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Pagination pagination, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = Arrays.stream(feeds) + .map(id -> id.toString()) + .toArray(String[]::new); + final RequestOption[] options = feedIDs.length == 0 + ? new RequestOption[] { pagination } + : new RequestOption[] { pagination, new CustomQueryParameter("filter", String.join(",", feedIDs)) }; + return client + .getFollowed(id, options) + .thenApply(response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture unfollow(FlatFeed feed) throws StreamException { + return unfollow(feed, io.getstream.core.KeepHistory.NO); + } + + public final CompletableFuture unfollow(FlatFeed feed, io.getstream.core.KeepHistory keepHistory) throws StreamException { + return client + .unfollow(id, feed.getID(), new io.getstream.core.options.KeepHistory(keepHistory)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture updateActivityToTargets(Activity activity, Iterable add, Iterable remove) throws StreamException { + return updateActivityToTargets(activity, Iterables.toArray(add, FeedID.class), Iterables.toArray(remove, FeedID.class)); + } + + public final CompletableFuture updateActivityToTargets(Activity activity, FeedID[] add, FeedID[] remove) throws StreamException { + return client + .updateActivityToTargets(id, activity, add, remove, new FeedID[0]) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture replaceActivityToTargets(Activity activity, Iterable newTargets) throws StreamException { + return replaceActivityToTargets(activity, Iterables.toArray(newTargets, FeedID.class)); + } + + public final CompletableFuture replaceActivityToTargets(Activity activity, FeedID... newTargets) throws StreamException { + return client + .updateActivityToTargets(id, activity, new FeedID[0], new FeedID[0], newTargets) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/FileStorageClient.java b/src/main/java/io/getstream/client/FileStorageClient.java new file mode 100644 index 00000000..b7696b22 --- /dev/null +++ b/src/main/java/io/getstream/client/FileStorageClient.java @@ -0,0 +1,37 @@ +package io.getstream.client; + +import io.getstream.core.StreamFiles; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.utils.Auth.TokenAction; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import static io.getstream.core.utils.Auth.buildFilesToken; + +public final class FileStorageClient { + private final String secret; + private final StreamFiles files; + + FileStorageClient(String secret, StreamFiles files) { + this.secret = secret; + this.files = files; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return files.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return files.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.DELETE); + return files.delete(token, url); + } +} diff --git a/src/main/java/io/getstream/client/FlatFeed.java b/src/main/java/io/getstream/client/FlatFeed.java new file mode 100644 index 00000000..7ed5ac70 --- /dev/null +++ b/src/main/java/io/getstream/client/FlatFeed.java @@ -0,0 +1,225 @@ +package io.getstream.client; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public final class FlatFeed extends Feed { + FlatFeed(Client client, FeedID id) { + super(client, id); + } + + public CompletableFuture> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(String ranking) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, null); + } + + public CompletableFuture> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Filter filter, String ranking) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, ranking); + } + + public CompletableFuture> getActivities(Pagination pagination, String ranking) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, ranking); + } + + CompletableFuture> getActivities(Pagination pagination, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, String ranking) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, null); + } + + public CompletableFuture> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, Pagination pagination, String ranking) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter, String ranking) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, ranking); + } + + CompletableFuture> getCustomActivities(Class type, Pagination pagination, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, String ranking) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + CompletableFuture> getEnrichedActivities(Pagination pagination, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, flags, ranking); + } + + CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/ImageStorageClient.java b/src/main/java/io/getstream/client/ImageStorageClient.java new file mode 100644 index 00000000..0f358c24 --- /dev/null +++ b/src/main/java/io/getstream/client/ImageStorageClient.java @@ -0,0 +1,49 @@ +package io.getstream.client; + +import io.getstream.core.StreamImages; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.options.Crop; +import io.getstream.core.options.Resize; +import io.getstream.core.utils.Auth.TokenAction; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import static io.getstream.core.utils.Auth.buildFilesToken; + +public final class ImageStorageClient { + private final String secret; + private final StreamImages images; + + ImageStorageClient(String secret, StreamImages images) { + this.secret = secret; + this.images = images; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return images.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.WRITE); + return images.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.DELETE); + return images.delete(token, url); + } + + public CompletableFuture process(URL url, Crop crop) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.READ); + return images.process(token, url, crop); + } + + public CompletableFuture process(URL url, Resize resize) throws StreamException { + final Token token = buildFilesToken(secret, TokenAction.READ); + return images.process(token, url, resize); + } +} diff --git a/src/main/java/io/getstream/client/NotificationFeed.java b/src/main/java/io/getstream/client/NotificationFeed.java new file mode 100644 index 00000000..91703f4f --- /dev/null +++ b/src/main/java/io/getstream/client/NotificationFeed.java @@ -0,0 +1,257 @@ +package io.getstream.client; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.NotificationGroup; +import io.getstream.core.options.ActivityMarker; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Pagination; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public final class NotificationFeed extends AggregatedFeed { + NotificationFeed(Client client, FeedID id) { + super(client, id); + } + + @Override + public CompletableFuture>> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + public CompletableFuture>> getActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + @Override + public CompletableFuture>> getActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + CompletableFuture>> getActivities(Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + CompletableFuture>> getCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + CompletableFuture>> getEnrichedActivities(Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/client/PersonalizationClient.java b/src/main/java/io/getstream/client/PersonalizationClient.java new file mode 100644 index 00000000..e7ed80fe --- /dev/null +++ b/src/main/java/io/getstream/client/PersonalizationClient.java @@ -0,0 +1,73 @@ +package io.getstream.client; + +import com.google.common.collect.ImmutableMap; +import io.getstream.core.StreamPersonalization; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.utils.Auth.TokenAction; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static io.getstream.core.utils.Auth.buildPersonalizationToken; + +public final class PersonalizationClient { + private final String secret; + private final StreamPersonalization personalization; + + PersonalizationClient(String secret, StreamPersonalization personalization) { + this.secret = secret; + this.personalization = personalization; + } + + public CompletableFuture> get(String resource) throws StreamException { + return get(null, resource); + } + + public CompletableFuture> get(String resource, Map params) throws StreamException { + return get(null, resource, params); + } + + public CompletableFuture> get(String userID, String resource) throws StreamException { + return get(userID, resource, ImmutableMap.of()); + } + + public CompletableFuture> get(String userID, String resource, Map params) throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.READ); + return personalization.get(token, userID, resource, params); + } + + public CompletableFuture post(String resource, Map payload) throws StreamException { + return post(null, resource, payload); + } + + public CompletableFuture post(String resource, Map params, Map payload) throws StreamException { + return post(null, resource, params, payload); + } + + public CompletableFuture post(String userID, String resource, Map payload) throws StreamException { + return post(userID, resource, ImmutableMap.of(), payload); + } + + public CompletableFuture post(String userID, String resource, Map params, Map payload) throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.WRITE); + return personalization.post(token, userID, resource, params, payload); + } + + public CompletableFuture delete(String resource) throws StreamException { + return delete(null, resource); + } + + public CompletableFuture delete(String resource, Map params) throws StreamException { + return delete(null, resource, params); + } + + public CompletableFuture delete(String userID, String resource) throws StreamException { + return delete(userID, resource, ImmutableMap.of()); + } + + public CompletableFuture delete(String userID, String resource, Map params) throws StreamException { + final Token token = buildPersonalizationToken(secret, userID, TokenAction.DELETE); + return personalization.delete(token, userID, resource, params); + } +} diff --git a/src/main/java/io/getstream/client/ReactionsClient.java b/src/main/java/io/getstream/client/ReactionsClient.java new file mode 100644 index 00000000..dcd00ae2 --- /dev/null +++ b/src/main/java/io/getstream/client/ReactionsClient.java @@ -0,0 +1,118 @@ +package io.getstream.client; + +import com.google.common.collect.Iterables; +import io.getstream.core.LookupKind; +import io.getstream.core.StreamReactions; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Reaction; +import io.getstream.core.options.Filter; +import io.getstream.core.utils.Auth.TokenAction; +import io.getstream.core.utils.DefaultOptions; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Auth.buildReactionsToken; + +public final class ReactionsClient { + private final String secret; + private final StreamReactions reactions; + + ReactionsClient(String secret, StreamReactions reactions) { + this.secret = secret; + this.reactions = reactions; + } + + public CompletableFuture get(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.get(token, id); + } + + public CompletableFuture> filter(LookupKind lookup, String id) throws StreamException { + return filter(lookup, id, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter) throws StreamException { + return filter(lookup, id, filter, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, String kind) throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, kind); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter, String kind) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.READ); + return reactions.filter(token, lookup, id, filter, kind); + } + + public CompletableFuture add(String userID, String kind, String activityID, Iterable targetFeeds) throws StreamException { + return add(userID, kind, activityID, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, String kind, String activityID, FeedID... targetFeeds) throws StreamException { + checkNotNull(kind, "Reaction kind can't be null"); + checkArgument(!kind.isEmpty(), "Reaction kind can't be empty"); + checkNotNull(activityID, "Reaction activity id can't be null"); + checkArgument(!activityID.isEmpty(), "Reaction activity id can't be empty"); + + return add(userID, Reaction.builder().activityID(activityID).kind(kind).build(), targetFeeds); + } + + public CompletableFuture add(String userID, Reaction reaction, Iterable targetFeeds) throws StreamException { + return add(userID, reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.add(token, userID, reaction, targetFeeds); + } + + public CompletableFuture addChild(String userID, String kind, String parentID, Iterable targetFeeds) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild(String userID, String kind, String parentID, FeedID... targetFeeds) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild(String userID, String parentID, Reaction reaction, Iterable targetFeeds) throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture addChild(String userID, String parentID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds); + } + + public CompletableFuture update(String id, Iterable targetFeeds) throws StreamException { + return update(id, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(String id, FeedID... targetFeeds) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + return update(Reaction.builder().id(id).build(), targetFeeds); + } + + public CompletableFuture update(Reaction reaction, Iterable targetFeeds) throws StreamException { + return update(reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(Reaction reaction, FeedID... targetFeeds) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.update(token, reaction, targetFeeds); + } + + public CompletableFuture delete(String id) throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.DELETE); + return reactions.delete(token, id); + } +} diff --git a/src/main/java/io/getstream/client/User.java b/src/main/java/io/getstream/client/User.java new file mode 100644 index 00000000..a6e88cc6 --- /dev/null +++ b/src/main/java/io/getstream/client/User.java @@ -0,0 +1,98 @@ +package io.getstream.client; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Data; +import io.getstream.core.models.ProfileData; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +public final class User { + private final Client client; + private final String id; + + public User(Client client, String id) { + checkNotNull(client, "Client can't be null"); + checkNotNull(id, "User ID can't be null"); + checkArgument(!id.isEmpty(), "User ID can't be empty"); + + this.client = client; + this.id = id; + } + + public String getID() { + return id; + } + + public CompletableFuture get() throws StreamException { + return client.getUser(id) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture delete() throws StreamException { + return client.deleteUser(id) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture getOrCreate(Data data) throws StreamException { + return client.getOrCreateUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture create(Data data) throws StreamException { + return client.createUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture update(Data data) throws StreamException { + return client.updateUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture profile() throws StreamException { + return client.userProfile(id) + .thenApply(response -> { + try { + return deserialize(response, ProfileData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java b/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java new file mode 100644 index 00000000..4b6dca67 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudAggregatedFeed.java @@ -0,0 +1,217 @@ +package io.getstream.cloud; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Group; +import io.getstream.core.options.ActivityMarker; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Pagination; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public class CloudAggregatedFeed extends CloudFeed { + CloudAggregatedFeed(CloudClient client, FeedID id) { + super(client, id); + } + + public CompletableFuture>> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getActivities(ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + public CompletableFuture>> getActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getActivities(Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + public CompletableFuture>> getCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + public CompletableFuture>> getCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + CompletableFuture>> getCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedActivities(Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, Group.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java b/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java new file mode 100644 index 00000000..62652afb --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudAnalyticsClient.java @@ -0,0 +1,32 @@ +package io.getstream.cloud; + +import com.google.common.collect.Iterables; +import io.getstream.core.StreamAnalytics; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; + +import java.util.concurrent.CompletableFuture; + +public final class CloudAnalyticsClient { + private final Token token; + private final StreamAnalytics analytics; + + CloudAnalyticsClient(Token token, StreamAnalytics analytics) { + this.token = token; + this.analytics = analytics; + } + + public CompletableFuture trackEngagement(Iterable events) throws StreamException { + return trackEngagement(Iterables.toArray(events, Engagement.class)); + } + + public CompletableFuture trackEngagement(Engagement... events) throws StreamException { + return analytics.trackEngagement(token, events); + } + + public CompletableFuture trackImpression(Impression event) throws StreamException { + return analytics.trackImpression(token, event); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudClient.java b/src/main/java/io/getstream/cloud/CloudClient.java new file mode 100644 index 00000000..b55810fa --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudClient.java @@ -0,0 +1,263 @@ +package io.getstream.cloud; + +import io.getstream.core.Region; +import io.getstream.core.Stream; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.OKHTTPClientAdapter; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.Activity; +import io.getstream.core.models.Data; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.OGData; +import io.getstream.core.options.RequestOption; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class CloudClient { + private final Token token; + private final String userID; + private final Stream stream; + + private CloudClient(String key, String token, String userID, URL baseURL, HTTPClient httpClient) { + this.token = new Token(token); + this.userID = userID; + this.stream = new Stream(key, baseURL, httpClient); + } + + public static Builder builder(String apiKey, String secret, String userID) { + return new Builder(apiKey, secret, userID); + } + + public CompletableFuture openGraph(URL url) throws StreamException { + return stream.openGraph(token, url); + } + + public static final class Builder { + private static final String DEFAULT_HOST = "stream-io-api.com"; + + private final String apiKey; + private final String token; + private final String userID; + private HTTPClient httpClient; + + private String scheme = "https"; + private String region = Region.US_EAST.toString(); + private String host = DEFAULT_HOST; + private int port = 443; + + public Builder(String apiKey, String token, String userID) { + checkNotNull(apiKey, "API key can't be null"); + checkNotNull(token, "Token can't be null"); + checkNotNull(userID, "User ID can't be null"); + checkArgument(!apiKey.isEmpty(), "API key can't be empty"); + checkArgument(!token.isEmpty(), "Token can't be empty"); + checkArgument(!userID.isEmpty(), "User ID can't be empty"); + this.apiKey = apiKey; + this.token = token; + this.userID = userID; + } + + public Builder httpClient(HTTPClient httpClient) { + checkNotNull(httpClient, "HTTP client can't be null"); + this.httpClient = httpClient; + return this; + } + + public Builder scheme(String scheme) { + checkNotNull(scheme, "Scheme can't be null"); + checkArgument(!scheme.isEmpty(), "Scheme can't be empty"); + this.scheme = scheme; + return this; + } + + public Builder host(String host) { + checkNotNull(host, "Host can't be null"); + checkArgument(!host.isEmpty(), "Host can't be empty"); + this.host = host; + return this; + } + + public Builder port(int port) { + checkArgument(port > 0, "Port has to be a non-zero positive number"); + this.port = port; + return this; + } + + public Builder region(Region region) { + checkNotNull(region, "Region can't be null"); + this.region = region.toString(); + return this; + } + + public Builder region(String region) { + checkNotNull(region, "Region can't be null"); + checkArgument(!region.isEmpty(), "Region can't be empty"); + this.region = region; + return this; + } + + private String buildHost() { + final StringBuilder sb = new StringBuilder(); + if (host.equals(DEFAULT_HOST)) { + sb.append(region).append("."); + } + sb.append(host); + return sb.toString(); + } + + public CloudClient build() throws MalformedURLException { + if (httpClient == null) { + httpClient = new OKHTTPClientAdapter(); + } + return new CloudClient(apiKey, token, userID, new URL(scheme, buildHost(), port, ""), httpClient); + } + } + + public T getHTTPClientImplementation() { + return stream.getHTTPClientImplementation(); + } + + //TODO: add personalized feed versions + public CloudFlatFeed flatFeed(String slug) { + return flatFeed(slug, userID); + } + + public CloudFlatFeed flatFeed(String slug, CloudUser user) { + return flatFeed(slug, user.getID()); + } + + public CloudFlatFeed flatFeed(String slug, String userID) { + return flatFeed(new FeedID(slug, userID)); + } + + public CloudFlatFeed flatFeed(FeedID id) { + return new CloudFlatFeed(this, id); + } + + public CloudAggregatedFeed aggregatedFeed(String slug) { + return aggregatedFeed(slug, userID); + } + + public CloudAggregatedFeed aggregatedFeed(String slug, CloudUser user) { + return aggregatedFeed(slug, user.getID()); + } + + public CloudAggregatedFeed aggregatedFeed(String slug, String userID) { + return aggregatedFeed(new FeedID(slug, userID)); + } + + public CloudAggregatedFeed aggregatedFeed(FeedID id) { + return new CloudAggregatedFeed(this, id); + } + + public CloudNotificationFeed notificationFeed(String slug) { + return notificationFeed(slug, userID); + } + + public CloudNotificationFeed notificationFeed(String slug, CloudUser user) { + return notificationFeed(slug, user.getID()); + } + + public CloudNotificationFeed notificationFeed(String slug, String userID) { + return notificationFeed(new FeedID(slug, userID)); + } + + public CloudNotificationFeed notificationFeed(FeedID id) { + return new CloudNotificationFeed(this, id); + } + + public CloudUser user(String userID) { + return new CloudUser(this, userID); + } + + public CloudAnalyticsClient analytics() { + return new CloudAnalyticsClient(token, stream.analytics()); + } + + public CloudCollectionsClient collections() { + return new CloudCollectionsClient(token, userID, stream.collections()); + } + + public CloudReactionsClient reactions() { + return new CloudReactionsClient(token, userID, stream.reactions()); + } + + public CloudFileStorageClient files() { + return new CloudFileStorageClient(token, stream.files()); + } + + public CloudImageStorageClient images() { + return new CloudImageStorageClient(token, stream.images()); + } + + CompletableFuture getActivities(FeedID feed, RequestOption... options) throws StreamException { + return stream.getActivities(token, feed, options); + } + + CompletableFuture getEnrichedActivities(FeedID feed, RequestOption... options) throws StreamException { + return stream.getEnrichedActivities(token, feed, options); + } + + CompletableFuture addActivity(FeedID feed, Activity activity) throws StreamException { + return stream.addActivity(token, feed, activity); + } + + CompletableFuture addActivities(FeedID feed, Activity... activities) throws StreamException { + return stream.addActivities(token, feed, activities); + } + + CompletableFuture removeActivityByID(FeedID feed, String id) throws StreamException { + return stream.removeActivityByID(token, feed, id); + } + + CompletableFuture removeActivityByForeignID(FeedID feed, String foreignID) throws StreamException { + return stream.removeActivityByForeignID(token, feed, foreignID); + } + + CompletableFuture follow(FeedID source, FeedID target, int activityCopyLimit) throws StreamException { + return stream.follow(token, token, source, target, activityCopyLimit); + } + + CompletableFuture getFollowers(FeedID feed, RequestOption... options) throws StreamException { + return stream.getFollowers(token, feed, options); + } + + CompletableFuture getFollowed(FeedID feed, RequestOption... options) throws StreamException { + return stream.getFollowed(token, feed, options); + } + + CompletableFuture unfollow(FeedID source, FeedID target, RequestOption... options) throws StreamException { + return stream.unfollow(token, source, target, options); + } + + CompletableFuture getUser(String id) throws StreamException { + return stream.getUser(token, id, false); + } + + CompletableFuture deleteUser(String id) throws StreamException { + return stream.deleteUser(token, id); + } + + CompletableFuture getOrCreateUser(String id, Data data) throws StreamException { + return stream.createUser(token, id, data, true); + } + + CompletableFuture createUser(String id, Data data) throws StreamException { + return stream.createUser(token, id, data, false); + } + + CompletableFuture updateUser(String id, Data data) throws StreamException { + return stream.updateUser(token, id, data); + } + + CompletableFuture userProfile(String id) throws StreamException { + return stream.getUser(token, id, true); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudCollectionsClient.java b/src/main/java/io/getstream/cloud/CloudCollectionsClient.java new file mode 100644 index 00000000..9ea483ad --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudCollectionsClient.java @@ -0,0 +1,81 @@ +package io.getstream.cloud; + +import io.getstream.core.StreamCollections; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static io.getstream.core.utils.Serialization.convert; + +public final class CloudCollectionsClient { + private final Token token; + private final String userID; + private final StreamCollections collections; + + CloudCollectionsClient(Token token, String userID, StreamCollections collections) { + this.token = token; + this.userID = userID; + this.collections = collections; + } + + public CompletableFuture addCustom(String collection, T item) throws StreamException { + return addCustom(userID, collection, item); + } + + public CompletableFuture addCustom(String userID, String collection, T item) throws StreamException { + return add(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture add(String collection, CollectionData item) throws StreamException { + return add(userID, collection, item); + } + + public CompletableFuture add(String userID, String collection, CollectionData item) throws StreamException { + return collections.add(token, userID, collection, item); + } + + public CompletableFuture updateCustom(String collection, T item) throws StreamException { + return updateCustom(userID, collection, item); + } + + public CompletableFuture updateCustom(String userID, String collection, T item) throws StreamException { + return update(userID, collection, convert(item, CollectionData.class)) + .thenApply(data -> convert(data, (Class) item.getClass())); + } + + public CompletableFuture update(String collection, CollectionData item) throws StreamException { + return update(userID, collection, item); + } + + public CompletableFuture update(String userID, String collection, CollectionData item) throws StreamException { + return collections.update(token, userID, collection, item); + } + + public CompletableFuture> customItems(Class type, String collection) throws StreamException { + return items(collection) + .thenApply(result -> result.stream() + .map(item -> convert(item, type)) + .collect(Collectors.toList())); + } + + public CompletableFuture> items(String collection) throws StreamException { + return collections.items(token, collection); + } + + public CompletableFuture getCustom(Class type, String collection, String id) throws StreamException { + return get(collection, id).thenApply(data -> convert(data, type)); + } + + public CompletableFuture get(String collection, String id) throws StreamException { + return collections.get(token, collection, id); + } + + public CompletableFuture delete(String collection, String id) throws StreamException { + return collections.delete(token, collection, id); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFeed.java b/src/main/java/io/getstream/cloud/CloudFeed.java new file mode 100644 index 00000000..47057cdc --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFeed.java @@ -0,0 +1,246 @@ +package io.getstream.cloud; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Streams; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.FollowRelation; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Pagination; +import io.getstream.core.options.RequestOption; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.*; + +public class CloudFeed { + private final CloudClient client; + private final FeedID id; + + CloudFeed(CloudClient client, FeedID id) { + checkNotNull(client, "Can't create feed w/o a client"); + checkNotNull(id, "Can't create feed w/o an ID"); + + this.client = client; + this.id = id; + } + + protected final CloudClient getClient() { + return client; + } + + public final FeedID getID() { + return id; + } + + public final String getSlug() { + return id.getSlug(); + } + + public final String getUserID() { + return id.getUserID(); + } + + public final CompletableFuture addActivity(Activity activity) throws StreamException { + return getClient() + .addActivity(id, activity) + .thenApply(response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture addCustomActivity(T activity) throws StreamException { + return getClient() + .addActivity(id, Activity.builder().fromCustomActivity(activity).build()) + .thenApply(response -> { + try { + return deserialize(response, (Class) activity.getClass()); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Iterable activities) throws StreamException { + return addActivities(Iterables.toArray(activities, Activity.class)); + } + + public final CompletableFuture> addCustomActivities(Iterable activities) throws StreamException { + final Activity[] custom = Streams.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply(response -> { + try { + Class element = (Class) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addActivities(Activity... activities) throws StreamException { + return getClient() + .addActivities(id, activities) + .thenApply(response -> { + try { + return deserializeContainer(response, "activities", Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> addCustomActivities(T... activities) throws StreamException { + final Activity[] custom = Arrays.stream(activities) + .map(activity -> Activity.builder().fromCustomActivity(activity).build()) + .toArray(Activity[]::new); + return getClient() + .addActivities(id, custom) + .thenApply(response -> { + try { + Class element = (Class) activities.getClass().getComponentType(); + return deserializeContainer(response, "activities", element); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByID(String id) throws StreamException { + return client + .removeActivityByID(this.id, id) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture removeActivityByForeignID(String foreignID) throws StreamException { + return client + .removeActivityByForeignID(id, foreignID) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture follow(CloudFlatFeed feed) throws StreamException { + return follow(feed, DefaultOptions.DEFAULT_ACTIVITY_COPY_LIMIT); + } + + public final CompletableFuture follow(CloudFlatFeed feed, int activityCopyLimit) throws StreamException { + checkArgument(activityCopyLimit <= DefaultOptions.MAX_ACTIVITY_COPY_LIMIT, String.format("Activity copy limit should be less then %d", DefaultOptions.MAX_ACTIVITY_COPY_LIMIT)); + + return client + .follow(id, feed.getID(), activityCopyLimit) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowers(Iterable feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_PAGINATION, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(FeedID... feedIDs) throws StreamException { + return getFollowers(DefaultOptions.DEFAULT_PAGINATION, feedIDs); + } + + public final CompletableFuture> getFollowers(Pagination pagination, Iterable feedIDs) throws StreamException { + return getFollowers(pagination, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowers(Pagination pagination, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = Arrays.stream(feeds) + .map(id -> id.toString()) + .toArray(String[]::new); + final RequestOption[] options = feedIDs.length == 0 + ? new RequestOption[] { pagination } + : new RequestOption[] { pagination, new CustomQueryParameter("filter", String.join(",", feedIDs)) }; + return client + .getFollowers(id, options) + .thenApply(response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture> getFollowed(Iterable feedIDs) throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_PAGINATION, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(FeedID... feedIDs) throws StreamException { + return getFollowed(DefaultOptions.DEFAULT_PAGINATION, feedIDs); + } + + public final CompletableFuture> getFollowed(Pagination pagination, Iterable feedIDs) throws StreamException { + return getFollowed(pagination, Iterables.toArray(feedIDs, FeedID.class)); + } + + public final CompletableFuture> getFollowed(Pagination pagination, FeedID... feeds) throws StreamException { + checkNotNull(feeds, "No feed ids to filter on"); + + final String[] feedIDs = Arrays.stream(feeds) + .map(id -> id.toString()) + .toArray(String[]::new); + final RequestOption[] options = feedIDs.length == 0 + ? new RequestOption[] { pagination } + : new RequestOption[] { pagination, new CustomQueryParameter("filter", String.join(",", feedIDs)) }; + return client + .getFollowed(id, options) + .thenApply(response -> { + try { + return deserializeContainer(response, FollowRelation.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public final CompletableFuture unfollow(CloudFlatFeed feed) throws StreamException { + return unfollow(feed, io.getstream.core.KeepHistory.NO); + } + + public final CompletableFuture unfollow(CloudFlatFeed feed, io.getstream.core.KeepHistory keepHistory) throws StreamException { + return client + .unfollow(id, feed.getID(), new io.getstream.core.options.KeepHistory(keepHistory)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFileStorageClient.java b/src/main/java/io/getstream/cloud/CloudFileStorageClient.java new file mode 100644 index 00000000..c2137c46 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFileStorageClient.java @@ -0,0 +1,31 @@ +package io.getstream.cloud; + +import io.getstream.core.StreamFiles; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +public final class CloudFileStorageClient { + private final Token token; + private final StreamFiles files; + + CloudFileStorageClient(Token token, StreamFiles files) { + this.token = token; + this.files = files; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + return files.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + return files.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + return files.delete(token, url); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudFlatFeed.java b/src/main/java/io/getstream/cloud/CloudFlatFeed.java new file mode 100644 index 00000000..ba534bea --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudFlatFeed.java @@ -0,0 +1,225 @@ +package io.getstream.cloud; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.options.*; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public final class CloudFlatFeed extends CloudFeed { + CloudFlatFeed(CloudClient client, FeedID id) { + super(client, id); + } + + public CompletableFuture> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(String ranking) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, null); + } + + public CompletableFuture> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getActivities(Filter filter, String ranking) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, ranking); + } + + public CompletableFuture> getActivities(Pagination pagination, String ranking) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, ranking); + } + + CompletableFuture> getActivities(Pagination pagination, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, String ranking) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, null); + } + + public CompletableFuture> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, null); + } + + public CompletableFuture> getCustomActivities(Class type, Pagination pagination, String ranking) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, ranking); + } + + public CompletableFuture> getCustomActivities(Class type, Filter filter, String ranking) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, ranking); + } + + CompletableFuture> getCustomActivities(Class type, Pagination pagination, Filter filter, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, String ranking) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedActivities(Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, flags, ranking); + } + + public CompletableFuture> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + CompletableFuture> getEnrichedActivities(Pagination pagination, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getEnrichedActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, flags, null); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, flags, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS, ranking); + } + + public CompletableFuture> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, flags, ranking); + } + + CompletableFuture> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, EnrichmentFlags flags, String ranking) throws StreamException { + final RequestOption[] options = ranking == null + ? new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER } + : new RequestOption[] { pagination, filter, flags, DefaultOptions.DEFAULT_MARKER, new Ranking(ranking) }; + return getClient() + .getActivities(getID(), options) + .thenApply(response -> { + try { + return deserializeContainer(response, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudImageStorageClient.java b/src/main/java/io/getstream/cloud/CloudImageStorageClient.java new file mode 100644 index 00000000..b73e4b2b --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudImageStorageClient.java @@ -0,0 +1,41 @@ +package io.getstream.cloud; + +import io.getstream.core.StreamImages; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.options.Crop; +import io.getstream.core.options.Resize; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +public final class CloudImageStorageClient { + private final Token token; + private final StreamImages images; + + CloudImageStorageClient(Token token, StreamImages images) { + this.token = token; + this.images = images; + } + + public CompletableFuture upload(String fileName, byte[] content) throws StreamException { + return images.upload(token, fileName, content); + } + + public CompletableFuture upload(File content) throws StreamException { + return images.upload(token, content); + } + + public CompletableFuture delete(URL url) throws StreamException { + return images.delete(token, url); + } + + public CompletableFuture process(URL url, Crop crop) throws StreamException { + return images.process(token, url, crop); + } + + public CompletableFuture process(URL url, Resize resize) throws StreamException { + return images.process(token, url, resize); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudNotificationFeed.java b/src/main/java/io/getstream/cloud/CloudNotificationFeed.java new file mode 100644 index 00000000..24fd5226 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudNotificationFeed.java @@ -0,0 +1,257 @@ +package io.getstream.cloud; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Activity; +import io.getstream.core.models.EnrichedActivity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.NotificationGroup; +import io.getstream.core.options.ActivityMarker; +import io.getstream.core.options.EnrichmentFlags; +import io.getstream.core.options.Filter; +import io.getstream.core.options.Pagination; +import io.getstream.core.utils.DefaultOptions; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static io.getstream.core.utils.Serialization.deserializeContainer; + +public final class CloudNotificationFeed extends CloudAggregatedFeed { + CloudNotificationFeed(CloudClient client, FeedID id) { + super(client, id); + } + + @Override + public CompletableFuture>> getActivities() throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(Pagination pagination) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(Filter filter) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getActivities(ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + public CompletableFuture>> getActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + @Override + public CompletableFuture>> getActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + CompletableFuture>> getActivities(Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Filter filter) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker); + } + + @Override + public CompletableFuture>> getCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker); + } + + @Override + CompletableFuture>> getCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker) throws StreamException { + return getClient() + .getActivities(getID(), pagination, filter, marker) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getEnrichedActivities() throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedActivities(Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedActivities(pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + CompletableFuture>> getEnrichedActivities(Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, EnrichedActivity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, DefaultOptions.DEFAULT_MARKER, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, DefaultOptions.DEFAULT_PAGINATION, filter, marker, flags); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, DefaultOptions.DEFAULT_ENRICHMENT_FLAGS); + } + + @Override + public CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getEnrichedCustomActivities(type, pagination, DefaultOptions.DEFAULT_FILTER, marker, flags); + } + + @Override + CompletableFuture>> getEnrichedCustomActivities(Class type, Pagination pagination, Filter filter, ActivityMarker marker, EnrichmentFlags flags) throws StreamException { + return getClient() + .getEnrichedActivities(getID(), pagination, filter, marker, flags) + .thenApply(response -> { + try { + return deserializeContainer(response, NotificationGroup.class, type); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudReactionsClient.java b/src/main/java/io/getstream/cloud/CloudReactionsClient.java new file mode 100644 index 00000000..c6aed665 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudReactionsClient.java @@ -0,0 +1,109 @@ +package io.getstream.cloud; + +import com.google.common.collect.Iterables; +import io.getstream.core.LookupKind; +import io.getstream.core.StreamReactions; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Reaction; +import io.getstream.core.options.Filter; +import io.getstream.core.utils.DefaultOptions; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class CloudReactionsClient { + private final Token token; + private final String userID; + private final StreamReactions reactions; + + CloudReactionsClient(Token token, String userID, StreamReactions reactions) { + this.token = token; + this.userID = userID; + this.reactions = reactions; + } + + public CompletableFuture get(String id) throws StreamException { + return reactions.get(token, id); + } + + public CompletableFuture> filter(LookupKind lookup, String id) throws StreamException { + return filter(lookup, id, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter) throws StreamException { + return filter(lookup, id, filter, ""); + } + + public CompletableFuture> filter(LookupKind lookup, String id, String kind) throws StreamException { + return filter(lookup, id, DefaultOptions.DEFAULT_FILTER, kind); + } + + public CompletableFuture> filter(LookupKind lookup, String id, Filter filter, String kind) throws StreamException { + return reactions.filter(token, lookup, id, filter, kind); + } + + public CompletableFuture add(String kind, String activityID, Iterable targetFeeds) throws StreamException { + return add(userID, kind, activityID, targetFeeds); + } + + public CompletableFuture add(String kind, String activityID, FeedID... targetFeeds) throws StreamException { + return add(userID, activityID, targetFeeds); + } + + public CompletableFuture add(Reaction reaction, Iterable targetFeeds) throws StreamException { + return add(userID, reaction, targetFeeds); + } + + public CompletableFuture add(Reaction reaction, FeedID... targetFeeds) throws StreamException { + return add(userID, reaction, targetFeeds); + } + + public CompletableFuture add(String userID, String kind, String activityID, Iterable targetFeeds) throws StreamException { + return add(userID, kind, activityID, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, String kind, String activityID, FeedID... targetFeeds) throws StreamException { + checkNotNull(kind, "Reaction kind can't be null"); + checkArgument(!kind.isEmpty(), "Reaction kind can't be empty"); + checkNotNull(activityID, "Reaction activity id can't be null"); + checkArgument(!activityID.isEmpty(), "Reaction activity id can't be empty"); + + return add(userID, Reaction.builder().activityID(activityID).kind(kind).build(), targetFeeds); + } + + public CompletableFuture add(String userID, Reaction reaction, Iterable targetFeeds) throws StreamException { + return add(userID, reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture add(String userID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + return reactions.add(token, userID, reaction, targetFeeds); + } + + public CompletableFuture update(String id, Iterable targetFeeds) throws StreamException { + return update(id, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(String id, FeedID... targetFeeds) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + return update(Reaction.builder().id(id).build(), targetFeeds); + } + + public CompletableFuture update(Reaction reaction, Iterable targetFeeds) throws StreamException { + return update(reaction, Iterables.toArray(targetFeeds, FeedID.class)); + } + + public CompletableFuture update(Reaction reaction, FeedID... targetFeeds) throws StreamException { + return reactions.update(token, reaction, targetFeeds); + } + + public CompletableFuture delete(String id) throws StreamException { + return reactions.delete(token, id); + } +} diff --git a/src/main/java/io/getstream/cloud/CloudUser.java b/src/main/java/io/getstream/cloud/CloudUser.java new file mode 100644 index 00000000..8e661fc2 --- /dev/null +++ b/src/main/java/io/getstream/cloud/CloudUser.java @@ -0,0 +1,98 @@ +package io.getstream.cloud; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.models.Data; +import io.getstream.core.models.ProfileData; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +public final class CloudUser { + private final CloudClient client; + private final String id; + + public CloudUser(CloudClient client, String id) { + checkNotNull(client, "Client can't be null"); + checkNotNull(id, "User ID can't be null"); + checkArgument(!id.isEmpty(), "User ID can't be empty"); + + this.client = client; + this.id = id; + } + + public String getID() { + return id; + } + + public CompletableFuture get() throws StreamException { + return client.getUser(id) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture delete() throws StreamException { + return client.deleteUser(id) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture getOrCreate(Data data) throws StreamException { + return client.getOrCreateUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture create(Data data) throws StreamException { + return client.createUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture update(Data data) throws StreamException { + return client.updateUser(id, data) + .thenApply(response -> { + try { + return deserialize(response, Data.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } + + public CompletableFuture profile() throws StreamException { + return client.userProfile(id) + .thenApply(response -> { + try { + return deserialize(response, ProfileData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/getstream/core/KeepHistory.java b/src/main/java/io/getstream/core/KeepHistory.java new file mode 100644 index 00000000..92ff2a86 --- /dev/null +++ b/src/main/java/io/getstream/core/KeepHistory.java @@ -0,0 +1,16 @@ +package io.getstream.core; + +public enum KeepHistory { + YES(true), + NO(false); + + private final boolean flag; + + KeepHistory(boolean flag) { + this.flag = flag; + } + + public boolean getFlag() { + return flag; + } +} diff --git a/src/main/java/io/getstream/core/LookupKind.java b/src/main/java/io/getstream/core/LookupKind.java new file mode 100644 index 00000000..a24dcc95 --- /dev/null +++ b/src/main/java/io/getstream/core/LookupKind.java @@ -0,0 +1,18 @@ +package io.getstream.core; + +public enum LookupKind { + ACTIVITY("activity_id"), + ACTIVITY_WITH_DATA("activity_id"), + REACTION("reaction_id"), + USER("user_id"); + + private final String kind; + + LookupKind(String kind) { + this.kind = kind; + } + + public String getKind() { + return kind; + } +} diff --git a/src/main/java/io/getstream/core/Region.java b/src/main/java/io/getstream/core/Region.java new file mode 100644 index 00000000..ce393e6a --- /dev/null +++ b/src/main/java/io/getstream/core/Region.java @@ -0,0 +1,20 @@ +package io.getstream.core; + +public enum Region { + US_EAST("us-east-api"), + TOKYO("tokyo-api"), + DUBLIN("dublin-api"), + SINGAPORE("singapore-api"), + CANADA("ca-central-1"); + + private final String region; + + Region(String region) { + this.region = region; + } + + @Override + public String toString() { + return region; + } +} diff --git a/src/main/java/io/getstream/core/Stream.java b/src/main/java/io/getstream/core/Stream.java new file mode 100644 index 00000000..9639fa72 --- /dev/null +++ b/src/main/java/io/getstream/core/Stream.java @@ -0,0 +1,369 @@ +package io.getstream.core; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Response; +import io.getstream.core.http.Token; +import io.getstream.core.models.Activity; +import io.getstream.core.models.Data; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.OGData; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.*; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.toJSON; + +public final class Stream { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + public Stream(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public StreamBatch batch() { + return new StreamBatch(key, baseURL, httpClient); + } + + public StreamCollections collections() { + return new StreamCollections(key, baseURL, httpClient); + } + + public StreamPersonalization personalization() { + return new StreamPersonalization(key, baseURL, httpClient); + } + + public StreamAnalytics analytics() { + return new StreamAnalytics(key, baseURL, httpClient); + } + + public StreamReactions reactions() { + return new StreamReactions(key, baseURL, httpClient); + } + + public StreamFiles files() { + return new StreamFiles(key, baseURL, httpClient); + } + + public StreamImages images() { + return new StreamImages(key, baseURL, httpClient); + } + + public CompletableFuture updateActivityByID(Token token, String id, Map set, String[] unset) throws StreamException { + checkNotNull(id, "No activity to update"); + checkNotNull(set, "No activity properties to set"); + checkNotNull(unset, "No activity properties to unset"); + + try { + //XXX: renaming variables so we can unambiguously name payload fields 'id', 'set', 'unset' + String activityID = id; + Map propertiesToSet = set; + String[] propertiesToUnset = unset; + final byte[] payload = toJSON(new Object() { + public final String id = activityID; + public final Map set = propertiesToSet; + public final String[] unset = propertiesToUnset; + }); + final URL url = new URL(baseURL, "activity/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivityByForeignID(Token token, String foreignID, Date timestamp, Map set, String[] unset) throws StreamException { + checkNotNull(foreignID, "No activity to update"); + checkNotNull(timestamp, "Missing timestamp"); + checkNotNull(set, "No activity properties to set"); + checkNotNull(unset, "No activity properties to unset"); + + try { + //XXX: renaming variables so we can unambiguously name payload fields 'set', 'unset' + Map propertiesToSet = set; + String[] propertiesToUnset = unset; + final byte[] payload = toJSON(new Object() { + public final String foreign_id = foreignID; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public final Date time = timestamp; + public final Map set = propertiesToSet; + public final String[] unset = propertiesToUnset; + }); + final URL url = new URL(baseURL, "activity/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserialize(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture openGraph(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "Missing url"); + + try { + final URL url = buildOpenGraphURL(baseURL); + return httpClient.execute(buildGet(url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply(response -> { + try { + return deserialize(response, OGData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public T getHTTPClientImplementation() { + return httpClient.getImplementation(); + } + + public CompletableFuture getActivities(Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getEnrichedActivities(Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildEnrichedFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture addActivity(Token token, FeedID feed, Activity activity) throws StreamException { + checkNotNull(activity, "No activity to add"); + + try { + final byte[] payload = toJSON(activity); + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture addActivities(Token token, FeedID feed, Activity... activityObjects) throws StreamException { + checkNotNull(activityObjects, "No activities to add"); + + try { + final byte[] payload = toJSON(new Object() { + public final Activity[] activities = activityObjects; + }); + final URL url = buildFeedURL(baseURL, feed, "/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture removeActivityByID(Token token, FeedID feed, String id) throws StreamException { + checkNotNull(id, "No activity id to remove"); + + try { + final URL url = buildFeedURL(baseURL, feed, '/' + id + '/'); + return httpClient.execute(buildDelete(url, key, token)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture removeActivityByForeignID(Token token, FeedID feed, String foreignID) throws StreamException { + checkNotNull(foreignID, "No activity id to remove"); + + try { + final URL url = buildFeedURL(baseURL, feed, '/' + foreignID + '/'); + return httpClient.execute(buildDelete(url, key, token, new CustomQueryParameter("foreign_id", "1"))); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture follow(Token token, Token targetToken, FeedID sourceFeed, FeedID targetFeed, int activityCopyLimit) throws StreamException { + checkNotNull(targetFeed, "No feed to follow"); + checkArgument(sourceFeed != targetFeed, "Feed can't follow itself"); + checkArgument(activityCopyLimit >= 0, "Activity copy limit should be a non-negative number"); + + try { + final byte[] payload = toJSON(new Object() { + public String target = targetFeed.toString(); + public int activity_copy_limit = activityCopyLimit; + public String target_token = targetToken.toString(); + }); + final URL url = buildFeedURL(baseURL, sourceFeed, "/following/"); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getFollowers(Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/followers/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getFollowed(Token token, FeedID feed, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + + try { + final URL url = buildFeedURL(baseURL, feed, "/following/"); + return httpClient.execute(buildGet(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture unfollow(Token token, FeedID source, FeedID target, RequestOption... options) throws StreamException { + checkNotNull(options, "Missing request options"); + checkNotNull(target, "No target feed to unfollow"); + + try { + final URL url = buildFeedURL(baseURL, source, "/following/" + target + '/'); + return httpClient.execute(buildDelete(url, key, token, options)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivityToTargets(Token token, FeedID feed, Activity activity, FeedID[] add, FeedID[] remove, FeedID[] replace) throws StreamException { + checkNotNull(activity, "No activity to update"); + checkNotNull(activity.getForeignID(), "Activity is required to have foreign ID attribute"); + checkNotNull(activity.getTime(), "Activity is required to have time attribute"); + checkNotNull(add, "No targets to add"); + checkNotNull(remove, "No targets to remove"); + checkNotNull(replace, "No targets to set"); + boolean modification = replace.length == 0 && (add.length > 0 || remove.length > 0); + boolean replacement = replace.length > 0 && add.length == 0 && remove.length == 0; + checkArgument(modification || replacement, "Can't replace and modify activity to targets at the same time"); + + final String[] addedTargets = Arrays.stream(add) + .map(id -> id.toString()) + .toArray(String[]::new); + final String[] removedTargets = Arrays.stream(remove) + .map(id -> id.toString()) + .toArray(String[]::new); + final String[] newTargets = Arrays.stream(replace) + .map(id -> id.toString()) + .toArray(String[]::new); + + try { + final byte[] payload = toJSON(new Object() { + public String foreign_id = activity.getForeignID(); + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public Date time = activity.getTime(); + public String[] added_targets = addedTargets; + public String[] removed_targets = removedTargets; + public String[] new_targets = newTargets; + }); + final URL url = buildToTargetUpdateURL(baseURL, feed); + return httpClient.execute(buildPost(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture getUser(Token token, String id, boolean withFollowCounts) throws StreamException { + checkNotNull(id, "Missing user ID"); + checkArgument(!id.isEmpty(), "Missing user ID"); + + try { + final URL url = buildUsersURL(baseURL, id + '/'); + return httpClient.execute(buildGet(url, key, token, new CustomQueryParameter("with_follow_counts", Boolean.toString(withFollowCounts)))); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteUser(Token token, String id) throws StreamException { + checkNotNull(id, "Missing user ID"); + checkArgument(!id.isEmpty(), "Missing user ID"); + + try { + final URL url = buildUsersURL(baseURL, id + '/'); + return httpClient.execute(buildDelete(url, key, token)); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture createUser(Token token, String userID, Data userData, boolean getOrCreate) throws StreamException { + checkNotNull(userID, "Missing user ID"); + checkNotNull(userData, "Missing user data"); + checkArgument(!userID.isEmpty(), "Missing user ID"); + + try { + final byte[] payload = toJSON(new Object() { + public String id = userID; + public Map data = userData.getData(); + }); + final URL url = buildUsersURL(baseURL); + return httpClient.execute(buildPost(url, key, token, payload, new CustomQueryParameter("get_or_create", Boolean.toString(getOrCreate)))); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateUser(Token token, String userID, Data userData) throws StreamException { + checkNotNull(userID, "Missing user ID"); + checkNotNull(userData, "Missing user data"); + checkArgument(!userID.isEmpty(), "Missing user ID"); + + try { + final byte[] payload = toJSON(new Object() { + public Map data = userData.getData(); + }); + final URL url = buildUsersURL(baseURL, userID + '/'); + return httpClient.execute(buildPut(url, key, token, payload)); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamAnalytics.java b/src/main/java/io/getstream/core/StreamAnalytics.java new file mode 100644 index 00000000..f45b1a98 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamAnalytics.java @@ -0,0 +1,93 @@ +package io.getstream.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Charsets; +import com.google.common.collect.ObjectArrays; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.Engagement; +import io.getstream.core.models.Impression; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildPost; +import static io.getstream.core.utils.Routes.buildAnalyticsURL; +import static io.getstream.core.utils.Serialization.deserializeError; +import static io.getstream.core.utils.Serialization.toJSON; + +public final class StreamAnalytics { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamAnalytics(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture trackEngagement(Token token, Engagement... events) throws StreamException { + checkNotNull(events, "No events to track"); + checkArgument(events.length > 0, "No events to track"); + + try { + final byte[] payload = toJSON(new Object() { + public final Engagement[] content_list = events; + }); + final URL url = buildAnalyticsURL(baseURL, "engagement/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture trackImpression(Token token, Impression event) throws StreamException { + checkNotNull(event, "No events to track"); + + try { + final byte[] payload = toJSON(event); + final URL url = buildAnalyticsURL(baseURL, "impression/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public URL createRedirectURL(Token token, URL url, Impression[] impressions, Engagement[] engagements) throws StreamException { + try { + final byte[] events = toJSON(ObjectArrays.concat(impressions, engagements, Object.class)); + return HTTPClient.requestBuilder() + .url(buildAnalyticsURL(baseURL, "redirect/")) + .addQueryParameter("api_key", key) + .addQueryParameter("url", url.toExternalForm()) + .addQueryParameter("events", new String(events, Charsets.UTF_8)) + .addQueryParameter("auth_type", "jwt") + .addQueryParameter("authorization", token.toString()) + .build().getURL(); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamBatch.java b/src/main/java/io/getstream/core/StreamBatch.java new file mode 100644 index 00000000..5dd3adca --- /dev/null +++ b/src/main/java/io/getstream/core/StreamBatch.java @@ -0,0 +1,183 @@ +package io.getstream.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.FollowRelation; +import io.getstream.core.models.ForeignIDTimePair; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildGet; +import static io.getstream.core.utils.Request.buildPost; +import static io.getstream.core.utils.Routes.buildActivitiesURL; +import static io.getstream.core.utils.Serialization.*; + +public final class StreamBatch { + private static final SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd"); + + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + public StreamBatch(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture addToMany(Token token, Activity activity, FeedID... feeds) throws StreamException { + checkNotNull(activity, "Missing activity"); + checkNotNull(feeds, "No feeds to add to"); + checkArgument(feeds.length > 0, "No feeds to add to"); + + //XXX: renaming the variable so we can unambiguously name payload field 'activity' + Activity data = activity; + String[] feedIDs = Arrays.stream(feeds).map(feed -> feed.toString()).toArray(String[]::new); + try { + final byte[] payload = toJSON(new Object() { + public final Activity activity = data; + public final String[] feed_ids = feedIDs; + }); + final URL url = new URL(baseURL, "feed/add_to_many/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture followMany(Token token, int activityCopyLimit, FollowRelation... follows) throws StreamException { + checkArgument(activityCopyLimit >= 0, "Activity copy limit must be non negative"); + checkNotNull(follows, "No feeds to follow"); + checkArgument(follows.length > 0, "No feeds to follow"); + + try { + final byte[] payload = toJSON(follows); + final URL url = new URL(baseURL, "follow_many/"); + return httpClient.execute(buildPost(url, key, token, payload, new CustomQueryParameter("activity_copy_limit", Integer.toString(activityCopyLimit)))) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture unfollowMany(Token token, FollowRelation... follows) throws StreamException { + checkNotNull(follows, "No feeds to unfollow"); + checkArgument(follows.length > 0, "No feeds to unfollow"); + + try { + final byte[] payload = toJSON(follows); + final URL url = new URL(baseURL, "unfollow_many/"); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getActivitiesByID(Token token, String... activityIDs) throws StreamException { + checkNotNull(activityIDs, "No activities to update"); + checkArgument(activityIDs.length > 0, "No activities to update"); + + try { + final URL url = buildActivitiesURL(baseURL); + return httpClient.execute(buildGet(url, key, token, new CustomQueryParameter("ids", String.join(",", activityIDs)))) + .thenApply(response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> getActivitiesByForeignID(Token token, ForeignIDTimePair... activityIDTimePairs) throws StreamException { + checkNotNull(activityIDTimePairs, "No activities to get"); + checkArgument(activityIDTimePairs.length > 0, "No activities to get"); + + String[] foreignIDs = Arrays.stream(activityIDTimePairs) + .map(pair -> pair.getForeignID()) + .toArray(String[]::new); + String[] timestamps = Arrays.stream(activityIDTimePairs) + .map(pair -> timestampFormat.format(pair.getTime())) + .toArray(String[]::new); + try { + final URL url = buildActivitiesURL(baseURL); + final RequestOption[] options = new RequestOption[]{ + new CustomQueryParameter("foreign_ids", String.join(",", foreignIDs)), + new CustomQueryParameter("timestamps", String.join(",", timestamps)) + }; + return httpClient.execute(buildGet(url, key, token, options)) + .thenApply(response -> { + try { + return deserializeContainer(response, Activity.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture updateActivities(Token token, Activity... activities) throws StreamException { + checkNotNull(activities, "No activities to update"); + checkArgument(activities.length > 0, "No activities to update"); + + try { + //XXX: renaming the variable so we can unambiguously name payload field 'activities' + Activity[] data = activities; + final byte[] payload = toJSON(new Object() { + public final Activity[] activities = data; + }); + final URL url = buildActivitiesURL(baseURL); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamCollections.java b/src/main/java/io/getstream/core/StreamCollections.java new file mode 100644 index 00000000..6478b8a8 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamCollections.java @@ -0,0 +1,226 @@ +package io.getstream.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.CollectionData; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildBatchCollectionsURL; +import static io.getstream.core.utils.Routes.buildCollectionsURL; +import static io.getstream.core.utils.Serialization.*; + +public final class StreamCollections { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamCollections(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture add(Token token, String userID, String collection, CollectionData item) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(item, "Collection data can't be null"); + + try { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .put("data", item.getData()); + if (userID != null) { + builder.put("user_id", userID); + } + if (item.getID() != null) { + builder.put("id", item.getID()); + } + final byte[] payload = toJSON(builder.build()); + final URL url = buildCollectionsURL(baseURL, collection + '/'); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture update(Token token, String userID, String collection, CollectionData item) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(item, "Collection data can't be null"); + + try { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .put("data", item.getData()); + if (userID != null) { + builder.put("user_id", userID); + } + final byte[] payload = toJSON(builder.build()); + final URL url = buildCollectionsURL(baseURL, collection + '/' + item.getID() + '/'); + return httpClient.execute(buildPut(url, key, token, payload)) + .thenApply(response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upsert(Token token, String collection, CollectionData... items) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(items.length > 0, "Collection data can't be empty"); + + try { + final byte[] payload = toJSON(new Object() { + public final Map data = ImmutableMap.of(collection, items); + }); + final URL url = buildBatchCollectionsURL(baseURL); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> items(Token token, String collection) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + + try { + final URL url = buildCollectionsURL(baseURL, collection + '/'); + return httpClient.execute(buildGet(url, key, token)) + .thenApply(response -> { + try { + return deserializeContainer(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture get(Token token, String collection, String id) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(id, "Collection id can't be null"); + checkArgument(!id.isEmpty(), "Collection id can't be empty"); + + try { + final URL url = buildCollectionsURL(baseURL, collection + '/' + id + '/'); + return httpClient.execute(buildGet(url, key, token)) + .thenApply(response -> { + try { + return deserialize(response, CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> select(Token token, String collection, String... ids) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(ids.length > 0, "Collection ids can't be empty"); + + List foreignIDs = Arrays.stream(ids) + .map(id -> String.format("%s:%s", collection, id)) + .collect(Collectors.toList()); + try { + final URL url = buildBatchCollectionsURL(baseURL); + return httpClient.execute(buildGet(url, key, token, new CustomQueryParameter("foreign_ids", String.join(",", foreignIDs)))) + .thenApply(response -> { + try { + return deserializeContainer(response, "response.data", CollectionData.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, String collection, String id) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkNotNull(id, "Collection id can't be null"); + checkArgument(!id.isEmpty(), "Collection id can't be empty"); + + try { + final URL url = buildCollectionsURL(baseURL, collection + '/' + id + '/'); + return httpClient.execute(buildDelete(url, key, token)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture deleteMany(Token token, String collection, String... ids) throws StreamException { + checkNotNull(collection, "Collection name can't be null"); + checkArgument(!collection.isEmpty(), "Collection name can't be empty"); + checkArgument(ids.length > 0, "Collection ids can't be empty"); + + try { + final URL url = buildBatchCollectionsURL(baseURL); + final RequestOption[] options = new RequestOption[] { + new CustomQueryParameter("collection_name", collection), + new CustomQueryParameter("ids", String.join(",", ids)) + }; + return httpClient.execute(buildDelete(url, key, token, options)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamFiles.java b/src/main/java/io/getstream/core/StreamFiles.java new file mode 100644 index 00000000..cf35d2a6 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamFiles.java @@ -0,0 +1,90 @@ +package io.getstream.core; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.buildDelete; +import static io.getstream.core.utils.Request.buildMultiPartPost; +import static io.getstream.core.utils.Routes.buildFilesURL; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +public class StreamFiles { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamFiles(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture upload(Token token, String fileName, byte[] content) throws StreamException { + checkNotNull(content, "No data to upload"); + checkArgument(content.length > 0, "No data to upload"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient.execute(buildMultiPartPost(url, key, token, fileName, content)) + .thenApply(response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upload(Token token, File content) throws StreamException { + checkNotNull(content, "No file to upload"); + checkArgument(content.exists(), "No file to upload"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient.execute(buildMultiPartPost(url, key, token, content)) + .thenApply(response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "No file to delete"); + + try { + final URL url = buildFilesURL(baseURL); + return httpClient.execute(buildDelete(url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamImages.java b/src/main/java/io/getstream/core/StreamImages.java new file mode 100644 index 00000000..662d1ac6 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamImages.java @@ -0,0 +1,108 @@ +package io.getstream.core; + +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildImagesURL; +import static io.getstream.core.utils.Serialization.deserialize; +import static io.getstream.core.utils.Serialization.deserializeError; + +public class StreamImages { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamImages(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture upload(Token token, String fileName, byte[] content) throws StreamException { + checkNotNull(content, "No data to upload"); + checkArgument(content.length > 0, "No data to upload"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient.execute(buildMultiPartPost(url, key, token, fileName, content)) + .thenApply(response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture upload(Token token, File content) throws StreamException { + checkNotNull(content, "No file to upload"); + checkArgument(content.exists(), "No file to upload"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient.execute(buildMultiPartPost(url, key, token, content)) + .thenApply(response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, URL targetURL) throws StreamException { + checkNotNull(targetURL, "No image to delete"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient.execute(buildDelete(url, key, token, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture process(Token token, URL targetURL, RequestOption option) throws StreamException { + checkNotNull(targetURL, "No image to process"); + + try { + final URL url = buildImagesURL(baseURL); + return httpClient.execute(buildGet(url, key, token, option, new CustomQueryParameter("url", targetURL.toExternalForm()))) + .thenApply(response -> { + try { + return deserialize(response, "file", URL.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamPersonalization.java b/src/main/java/io/getstream/core/StreamPersonalization.java new file mode 100644 index 00000000..929d24b7 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamPersonalization.java @@ -0,0 +1,108 @@ +package io.getstream.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.RequestOption; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildPersonalizationURL; +import static io.getstream.core.utils.Serialization.*; + +public final class StreamPersonalization { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamPersonalization(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture> get(Token token, String userID, String resource, Map params) throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + + try { + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = params.entrySet().stream() + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient.execute(buildGet(url, key, token, options)) + .thenApply(response -> { + try { + return deserialize(response, new TypeReference>() {}); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture post(Token token, String userID, String resource, Map params, Map payload) throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + checkNotNull(params, "Missing payload"); + + try { + final byte[] jsonPayload = toJSON(new Object() { + public final Map data = payload; + }); + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = params.entrySet().stream() + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient.execute(buildPost(url, key, token, jsonPayload, options)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, String userID, String resource, Map params) throws StreamException { + checkNotNull(resource, "Resource can't be empty"); + checkArgument(!resource.isEmpty(), "Resource can't be empty"); + checkNotNull(params, "Missing params"); + + try { + final URL url = buildPersonalizationURL(baseURL, resource + '/'); + final RequestOption[] options = params.entrySet().stream() + .map(entry -> new CustomQueryParameter(entry.getKey(), entry.getValue().toString())) + .toArray(RequestOption[]::new); + return httpClient.execute(buildDelete(url, key, token, options)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/StreamReactions.java b/src/main/java/io/getstream/core/StreamReactions.java new file mode 100644 index 00000000..47089620 --- /dev/null +++ b/src/main/java/io/getstream/core/StreamReactions.java @@ -0,0 +1,182 @@ +package io.getstream.core; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import io.getstream.core.exceptions.StreamException; +import io.getstream.core.http.HTTPClient; +import io.getstream.core.http.Token; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Reaction; +import io.getstream.core.options.CustomQueryParameter; +import io.getstream.core.options.Filter; +import io.getstream.core.options.RequestOption; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Request.*; +import static io.getstream.core.utils.Routes.buildReactionsURL; +import static io.getstream.core.utils.Serialization.*; + +public final class StreamReactions { + private final String key; + private final URL baseURL; + private final HTTPClient httpClient; + + StreamReactions(String key, URL baseURL, HTTPClient httpClient) { + this.key = key; + this.baseURL = baseURL; + this.httpClient = httpClient; + } + + public CompletableFuture get(Token token, String id) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + '/'); + return httpClient.execute(buildGet(url, key, token)) + .thenApply(response -> { + try { + return deserialize(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture> filter(Token token, LookupKind lookup, String id, Filter filter, String kind) throws StreamException { + checkNotNull(lookup, "Lookup kind can't be null"); + checkNotNull(id, "Reaction ID can't be null"); + checkArgument(!id.isEmpty(), "Reaction ID can't be empty"); + checkNotNull(filter, "Filter can't be null"); + checkNotNull(kind, "Kind can't be null"); + + try { + final URL url = buildReactionsURL(baseURL, lookup.getKind() + '/' + id + '/'); + RequestOption reactionType = new CustomQueryParameter("kind", kind); + RequestOption withActivityData = new CustomQueryParameter("with_activity_data", Boolean.toString(lookup == LookupKind.ACTIVITY_WITH_DATA)); + return httpClient.execute(buildGet(url, key, token, filter, reactionType, withActivityData)) + .thenApply(response -> { + try { + return deserializeContainer(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture add(Token token, String userID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + checkNotNull(reaction, "Reaction can't be null"); + checkArgument(reaction.getActivityID() != null || reaction.getParent() != null, "Reaction has to either have and activity ID or parent"); + checkArgument(reaction.getActivityID() == null || reaction.getParent() == null, "Reaction can't have both activity ID and parent"); + if (reaction.getActivityID() != null) { + checkArgument(!reaction.getActivityID().isEmpty(), "Reaction activity ID can't be empty"); + } + if (reaction.getParent() != null) { + checkArgument(!reaction.getParent().isEmpty(), "Reaction parent can't be empty"); + } + checkNotNull(reaction.getKind(), "Reaction kind can't be null"); + checkArgument(!reaction.getKind().isEmpty(), "Reaction kind can't be empty"); + + String[] targetFeedIDs = Arrays.stream(targetFeeds) + .map(feed -> feed.toString()) + .toArray(String[]::new); + + try { + ImmutableMap.Builder payloadBuilder = ImmutableMap.builder(); + payloadBuilder.put("kind", reaction.getKind()); + payloadBuilder.put("target_feeds", targetFeedIDs); + if (reaction.getActivityID() != null) { + payloadBuilder.put("activity_id", reaction.getActivityID()); + } + if (userID != null || reaction.getUserID() != null) { + payloadBuilder.put("user_id", firstNonNull(userID, reaction.getUserID())); + } + if (reaction.getParent() != null) { + payloadBuilder.put("parent", reaction.getParent()); + } + if (reaction.getId() != null) { + payloadBuilder.put("id", reaction.getId()); + } + if (reaction.getExtra() != null) { + payloadBuilder.put("data", reaction.getExtra()); + } + final byte[] payload = toJSON(payloadBuilder.build()); + final URL url = buildReactionsURL(baseURL); + return httpClient.execute(buildPost(url, key, token, payload)) + .thenApply(response -> { + try { + return deserialize(response, Reaction.class); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture update(Token token, Reaction reaction, FeedID... targetFeeds) throws StreamException { + checkNotNull(reaction, "Reaction can't be null"); + checkNotNull(reaction.getId(), "Reaction id can't be null"); + checkArgument(!reaction.getId().isEmpty(), "Reaction id can't be empty"); + + String[] targetFeedIDs = Arrays.stream(targetFeeds) + .map(feed -> feed.toString()) + .toArray(String[]::new); + + try { + final byte[] payload = toJSON(new Object() { + public final Map data = reaction.getExtra(); + public final String[] target_feeds = targetFeedIDs; + }); + final URL url = buildReactionsURL(baseURL, reaction.getId() + '/'); + return httpClient.execute(buildPut(url, key, token, payload)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (JsonProcessingException | MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } + + public CompletableFuture delete(Token token, String id) throws StreamException { + checkNotNull(id, "Reaction id can't be null"); + checkArgument(!id.isEmpty(), "Reaction id can't be empty"); + + try { + final URL url = buildReactionsURL(baseURL, id + '/'); + return httpClient.execute(buildDelete(url, key, token)) + .thenApply(response -> { + try { + return deserializeError(response); + } catch (StreamException | IOException e) { + throw new CompletionException(e); + } + }); + } catch (MalformedURLException | URISyntaxException e) { + throw new StreamException(e); + } + } +} diff --git a/src/main/java/io/getstream/core/exceptions/StreamAPIException.java b/src/main/java/io/getstream/core/exceptions/StreamAPIException.java new file mode 100644 index 00000000..ec8f15db --- /dev/null +++ b/src/main/java/io/getstream/core/exceptions/StreamAPIException.java @@ -0,0 +1,60 @@ +package io.getstream.core.exceptions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class StreamAPIException extends StreamException { + private final int errorCode; + private final int statusCode; + private final String errorName; + + @JsonCreator + public StreamAPIException( + @JsonProperty("detail") String message, + @JsonProperty("code") int errorCode, + @JsonProperty("status_code") int statusCode, + @JsonProperty("exception") String errorName) { + super(formatMessage(message, errorName, errorCode, statusCode)); + + this.errorCode = errorCode; + this.statusCode = statusCode; + this.errorName = errorName; + } + + private static String formatMessage(String message, String errorName, int errorCode, int statusCode) { + StringBuilder result = new StringBuilder(); + if (errorName != null && !errorName.isEmpty()) { + result.append(errorName); + } + if (message != null && !message.isEmpty()) { + if (result.length() > 0) { + result.append(": "); + } + + result.append(message); + } + if (result.length() > 0) { + result.append(" "); + } + result.append("(code = "); + result.append(errorCode); + result.append(" status = "); + result.append(statusCode); + result.append(')'); + return result.toString(); + } + + public int getErrorCode() { + return errorCode; + } + + public int getStatusCode() { + return statusCode; + } + + public String getErrorName() { + return errorName; + } +} diff --git a/src/main/java/io/getstream/core/exceptions/StreamException.java b/src/main/java/io/getstream/core/exceptions/StreamException.java new file mode 100644 index 00000000..e9a027d8 --- /dev/null +++ b/src/main/java/io/getstream/core/exceptions/StreamException.java @@ -0,0 +1,23 @@ +package io.getstream.core.exceptions; + +public class StreamException extends Exception { + public StreamException() { + super(); + } + + public StreamException(String message) { + super(message); + } + + public StreamException(String message, Throwable cause) { + super(message, cause); + } + + public StreamException(Throwable cause) { + super(cause); + } + + protected StreamException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/io/getstream/core/http/HTTPClient.java b/src/main/java/io/getstream/core/http/HTTPClient.java new file mode 100644 index 00000000..b699849e --- /dev/null +++ b/src/main/java/io/getstream/core/http/HTTPClient.java @@ -0,0 +1,12 @@ +package io.getstream.core.http; + +import java.util.concurrent.CompletableFuture; + +public abstract class HTTPClient { + public static Request.Builder requestBuilder() { + return Request.builder(); + } + + public abstract T getImplementation(); + public abstract CompletableFuture execute(Request request); +} diff --git a/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java b/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java new file mode 100644 index 00000000..cd3739ce --- /dev/null +++ b/src/main/java/io/getstream/core/http/OKHTTPClientAdapter.java @@ -0,0 +1,114 @@ +package io.getstream.core.http; + +import io.getstream.core.utils.Info; +import okhttp3.*; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.util.concurrent.CompletableFuture; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class OKHTTPClientAdapter extends HTTPClient { + private static final String userAgentTemplate = "okhttp3 stream-java2 %s v%s"; + + private final OkHttpClient client; + + public OKHTTPClientAdapter() { + this.client = new OkHttpClient.Builder() + .followRedirects(false) + .followSslRedirects(false) + .build(); + } + + public OKHTTPClientAdapter(OkHttpClient client) { + checkNotNull(client); + this.client = client; + } + + @Override + public T getImplementation() { + return (T) client; + } + + private okhttp3.RequestBody buildOkHttpRequestBody(io.getstream.core.http.RequestBody body) { + okhttp3.RequestBody okBody = null; + MediaType mediaType; + switch (body.getType()) { + case JSON: + mediaType = MediaType.parse(body.getType().toString()); + okBody = okhttp3.RequestBody.create(mediaType, body.getBytes()); + break; + case MULTI_PART: + String mimeType = URLConnection.guessContentTypeFromName(body.getFileName()); + mediaType = MediaType.parse(mimeType); + MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); + if (body.getBytes() != null) { + builder.addFormDataPart("file", body.getFileName(), okhttp3.RequestBody.create(mediaType, body.getBytes())); + } else { + builder.addFormDataPart("file", body.getFileName(), okhttp3.RequestBody.create(mediaType, body.getFile())); + } + okBody = builder.build(); + break; + } + return okBody; + } + + private okhttp3.Request buildOkHttpRequest(io.getstream.core.http.Request request) { + String version = Info.getProperties().getProperty(Info.VERSION); + String userAgent = String.format(userAgentTemplate, System.getProperty("os.name"), version); + okhttp3.Request.Builder builder = new okhttp3.Request.Builder() + .url(request.getURL()) + .addHeader("Stream-Auth-Type", "jwt") + .addHeader("Authorization", request.getToken().toString()) + .addHeader("User-Agent", userAgent); + + MediaType mediaType; + switch (request.getMethod()) { + case GET: + builder.get(); + break; + case DELETE: + builder.delete(); + break; + case PUT: + builder.put(buildOkHttpRequestBody(request.getBody())); + break; + case POST: + builder.post(buildOkHttpRequestBody(request.getBody())); + break; + } + return builder.build(); + } + + private io.getstream.core.http.Response buildResponse(okhttp3.Response response) { + final InputStream body = response.body() != null ? response.body().byteStream() : null; + return new io.getstream.core.http.Response(response.code(), body); + } + + @Override + public CompletableFuture execute(io.getstream.core.http.Request request) { + final CompletableFuture result = new CompletableFuture<>(); + + client.newCall(buildOkHttpRequest(request)).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + result.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, okhttp3.Response response) { + io.getstream.core.http.Response httpResponse = buildResponse(response); + try (InputStream ignored = httpResponse.getBody()){ + result.complete(httpResponse); + } catch (Exception e) { + result.completeExceptionally(e); + } + } + }); + + return result; + } +} + diff --git a/src/main/java/io/getstream/core/http/Request.java b/src/main/java/io/getstream/core/http/Request.java new file mode 100644 index 00000000..b16629db --- /dev/null +++ b/src/main/java/io/getstream/core/http/Request.java @@ -0,0 +1,157 @@ +package io.getstream.core.http; + +import com.google.common.base.MoreObjects; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Objects; + +public final class Request { + private final Token token; + private final URL url; + private final Method method; + private final RequestBody body; + + private Request(Builder builder) throws MalformedURLException { + token = builder.token; + url = builder.uri.toURL(); + method = builder.method; + body = builder.body; + } + + public Token getToken() { + return token; + } + + public URL getURL() { + return url; + } + + public Method getMethod() { + return method; + } + + public RequestBody getBody() { + return body; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Request request = (Request) o; + return Objects.equals(token, request.token) && + Objects.equals(url, request.url) && + method == request.method && + Objects.equals(body, request.body); + } + + @Override + public int hashCode() { + return Objects.hash(token, url, method, body); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("token", this.token) + .add("url", this.url) + .add("method", this.method) + .add("body", this.body) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + public enum Method { + GET, + POST, + PUT, + DELETE + } + + public static final class Builder { + private Token token; + private URI uri; + private StringBuilder query; + private Method method; + private RequestBody body; + + public Builder token(Token token) { + this.token = token; + return this; + } + + public Builder url(URL url) throws URISyntaxException { + uri = url.toURI(); + if (uri.getQuery() != null) { + query = new StringBuilder(uri.getQuery()); + } else { + query = new StringBuilder(); + } + return this; + } + + public Builder addQueryParameter(String key, String value) { + if (query.length() > 0) { + query.append('&'); + } + query.append(key); + query.append('='); + query.append(value); + return this; + } + + public Builder get() { + this.method = Method.GET; + this.body = null; + return this; + } + + public Builder post(byte[] body) { + this.method = Method.POST; + this.body = new RequestBody(body, RequestBody.Type.JSON); + return this; + } + + public Builder multiPartPost(String fileName, byte[] body) { + this.method = Method.POST; + this.body = new RequestBody(fileName, body, RequestBody.Type.MULTI_PART); + return this; + } + + public Builder multiPartPost(File body) { + this.method = Method.POST; + this.body = new RequestBody(body, RequestBody.Type.MULTI_PART); + return this; + } + + public Builder put(byte[] body) { + this.method = Method.PUT; + this.body = new RequestBody(body, RequestBody.Type.JSON); + return this; + } + + public Builder delete() { + this.method = Method.DELETE; + this.body = null; + return this; + } + + public Request build() throws MalformedURLException, URISyntaxException { + this.uri = new URI(uri.getScheme(), + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + uri.getPath(), + query.toString(), + null); + return new Request(this); + } + } +} diff --git a/src/main/java/io/getstream/core/http/RequestBody.java b/src/main/java/io/getstream/core/http/RequestBody.java new file mode 100644 index 00000000..515d08a5 --- /dev/null +++ b/src/main/java/io/getstream/core/http/RequestBody.java @@ -0,0 +1,93 @@ +package io.getstream.core.http; + +import com.google.common.base.MoreObjects; + +import java.io.File; +import java.util.Arrays; +import java.util.Objects; + +public final class RequestBody { + public enum Type { + JSON("application/json"), + MULTI_PART("multipart/form-data"); + + private final String type; + + Type(String type) { + this.type = type; + } + + @Override + public String toString() { + return type; + } + } + + private final Type type; + private final byte[] bytes; + private final File file; + private final String fileName; + + RequestBody(byte[] bytes, Type type) { + this.type = type; + this.bytes = bytes; + this.file = null; + this.fileName = null; + } + + RequestBody(String fileName, byte[] bytes, Type type) { + this.type = type; + this.bytes = bytes; + this.file = null; + this.fileName = fileName; + } + + RequestBody(File file, Type type) { + this.type = type; + this.bytes = null; + this.file = file; + this.fileName = file.getName(); + } + + public Type getType() { + return type; + } + + public byte[] getBytes() { + return bytes; + } + + public File getFile() { + return file; + } + + public String getFileName() { + return fileName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RequestBody that = (RequestBody) o; + return type == that.type && + Arrays.equals(bytes, that.bytes) && + Objects.equals(file, that.file); + } + + @Override + public int hashCode() { + int result = Objects.hash(type, file); + result = 31 * result + Arrays.hashCode(bytes); + return result; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(RequestBody.class) + .add("type", type) + .add("bytes", bytes) + .add("file", file) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/http/Response.java b/src/main/java/io/getstream/core/http/Response.java new file mode 100644 index 00000000..c530059f --- /dev/null +++ b/src/main/java/io/getstream/core/http/Response.java @@ -0,0 +1,49 @@ +package io.getstream.core.http; + +import com.google.common.base.MoreObjects; + +import java.io.InputStream; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; + +public final class Response { + private final int code; + private final InputStream body; + + public Response(int code, InputStream body) { + checkArgument(code >= 100 && code <= 599, "Invalid HTTP status code"); + this.code = code; + this.body = body; + } + + public int getCode() { + return code; + } + + public InputStream getBody() { + return body; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Response response = (Response) o; + return code == response.code && + Objects.equals(body, response.body); + } + + @Override + public int hashCode() { + return Objects.hash(code, body); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("code", this.code) + .add("body", this.body) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/http/Token.java b/src/main/java/io/getstream/core/http/Token.java new file mode 100644 index 00000000..32a294b8 --- /dev/null +++ b/src/main/java/io/getstream/core/http/Token.java @@ -0,0 +1,35 @@ +package io.getstream.core.http; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public final class Token { + private final String token; + + public Token(String token) { + checkNotNull(token, "Token can't be null"); + checkArgument(!token.isEmpty(), "Token can't be null"); + + this.token = token; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Token token1 = (Token) o; + return Objects.equals(token, token1.token); + } + + @Override + public int hashCode() { + return Objects.hash(token); + } + + @Override + public String toString() { + return token; + } +} diff --git a/src/main/java/io/getstream/core/models/Activity.java b/src/main/java/io/getstream/core/models/Activity.java new file mode 100644 index 00000000..3644e633 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Activity.java @@ -0,0 +1,265 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Activity.Builder.class) +public class Activity { + private final String id; + private final String actor; + private final String verb; + private final String object; + private final String foreignID; + private final String target; + //TODO: support Java 8 Date/Time types? + private final Date time; + private final String origin; + private final List to; + private final Double score; + private final Map extra; + + private Activity(Builder builder) { + id = builder.id; + actor = builder.actor; + verb = builder.verb; + object = builder.object; + foreignID = builder.foreignID; + target = builder.target; + time = builder.time; + origin = builder.origin; + to = builder.to; + score = builder.score; + extra = builder.extra; + } + + public String getID() { + return id; + } + + public String getActor() { + return actor; + } + + public String getVerb() { + return verb; + } + + public String getObject() { + return object; + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + public String getTarget() { + return target; + } + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public Date getTime() { + return time; + } + + public String getOrigin() { + return origin; + } + + public List getTo() { + return to; + } + + public Double getScore() { + return score; + } + + @JsonAnyGetter + public Map getExtra() { + return extra; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Activity activity = (Activity) o; + return Objects.equals(id, activity.id) && + Objects.equals(actor, activity.actor) && + Objects.equals(verb, activity.verb) && + Objects.equals(object, activity.object) && + Objects.equals(foreignID, activity.foreignID) && + Objects.equals(target, activity.target) && + Objects.equals(time, activity.time) && + Objects.equals(origin, activity.origin) && + Objects.equals(to, activity.to) && + Objects.equals(score, activity.score) && + Objects.equals(extra, activity.extra); + } + + @Override + public int hashCode() { + return Objects.hash(id, actor, verb, object, foreignID, target, time, origin, to, score, extra); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("actor", this.actor) + .add("verb", this.verb) + .add("object", this.object) + .add("foreignID", this.foreignID) + .add("target", this.target) + .add("time", this.time) + .add("origin", this.origin) + .add("to", this.to) + .add("score", this.score) + .add("extra", this.extra) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String id; + private String actor; + private String verb; + private String object; + private String foreignID; + private String target; + private Date time; + private String origin; + private List to; + private Double score; + private Map extra; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder actor(String actor) { + this.actor = actor; + return this; + } + + public Builder verb(String verb) { + this.verb = verb; + return this; + } + + public Builder object(String object) { + this.object = object; + return this; + } + + @JsonProperty("foreign_id") + public Builder foreignID(String foreignID) { + this.foreignID = foreignID; + return this; + } + + public Builder target(String target) { + this.target = target; + return this; + } + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public Builder time(Date time) { + this.time = time; + return this; + } + + public Builder origin(String origin) { + this.origin = origin; + return this; + } + + @JsonProperty("to") + public Builder to(List to) { + this.to = to; + return this; + } + + @JsonIgnore + public Builder to(Iterable to) { + this.to = Lists.newArrayList(to); + return this; + } + + @JsonIgnore + public Builder to(FeedID... to) { + this.to = Lists.newArrayList(to); + return this; + } + + public Builder score(double score) { + this.score = score; + return this; + } + + @JsonAnySetter + public Builder extraField(String key, Object value) { + if (extra == null) { + extra = Maps.newHashMap(); + } + extra.put(key, value); + return this; + } + + @JsonIgnore + public Builder extra(Map extra) { + if (!extra.isEmpty()) { + this.extra = extra; + } + return this; + } + + @JsonIgnore + public Builder fromActivity(Activity activity) { + this.id = activity.id; + this.actor = activity.actor; + this.verb = activity.verb; + this.object = activity.object; + this.foreignID = activity.foreignID; + this.target = activity.target; + this.time = activity.time; + this.origin = activity.origin; + this.to = activity.to; + this.score = activity.score; + this.extra = activity.extra; + return this; + } + + @JsonIgnore + public Builder fromCustomActivity(T custom) { + return fromActivity(convert(custom, Activity.class)); + } + + public Activity build() { + checkNotNull(actor, "Activity 'actor' field required"); + checkNotNull(verb, "Activity 'verb' field required"); + checkNotNull(object, "Activity 'object' field required"); + + return new Activity(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/CollectionData.java b/src/main/java/io/getstream/core/models/CollectionData.java new file mode 100644 index 00000000..8b47952f --- /dev/null +++ b/src/main/java/io/getstream/core/models/CollectionData.java @@ -0,0 +1,105 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +public final class CollectionData { + private final String id; + private final String collection; + private final Map data; + + @JsonCreator + public CollectionData( + @JsonProperty("collection") + String collection, + @JsonProperty("id") + String id, + @JsonProperty("data") + Map data) { + this.collection = collection; + this.data = firstNonNull(data, Maps.newHashMap()); + this.id = checkNotNull(id, "ID required"); + } + + public CollectionData() { + this(null, "", null); + } + + public CollectionData(String id) { + this(null, id, null); + } + + public static CollectionData buildFrom(T data) { + return convert(data, CollectionData.class); + } + + public String getID() { + return id; + } + + @JsonIgnore + public String getCollection() { + return collection; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + @JsonAnySetter + public CollectionData set(String key, T value) { + checkArgument(!"id".equals(key), "Key can't be named 'id'"); + checkNotNull(key, "Key can't be null"); + checkNotNull(value, "Value can't be null"); + + data.put(key, value); + return this; + } + + public CollectionData from(T data) { + checkNotNull(data, "Can't extract data from null"); + + Map map = convert(data, new TypeReference>() {}); + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CollectionData collectionData = (CollectionData) o; + return Objects.equals(id, collectionData.id) && + Objects.equals(data, collectionData.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("data", this.data) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Content.java b/src/main/java/io/getstream/core/models/Content.java new file mode 100644 index 00000000..1f0426ab --- /dev/null +++ b/src/main/java/io/getstream/core/models/Content.java @@ -0,0 +1,86 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +public final class Content { + private final String foreignID; + private final Map data = Maps.newHashMap(); + + @JsonCreator + public Content(@JsonProperty("foreign_id") String foreignID) { + this.foreignID = checkNotNull(foreignID, "ID required"); + } + + public static Content buildFrom(T data) { + return convert(data, Content.class); + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + @JsonAnySetter + public Content set(String key, T value) { + checkArgument(!"foreignID".equals(key), "Key can't be named 'foreignID'"); + checkNotNull(key, "Key can't be null"); + checkNotNull(value, "Value can't be null"); + + data.put(key, value); + return this; + } + + public Content from(T data) { + checkNotNull(data, "Can't extract data from null"); + + Map map = convert(data, new TypeReference>() {}); + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Content collectionData = (Content) o; + return Objects.equals(foreignID, collectionData.foreignID) && + Objects.equals(data, collectionData.data); + } + + @Override + public int hashCode() { + return Objects.hash(foreignID, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.foreignID) + .add("data", this.data) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Data.java b/src/main/java/io/getstream/core/models/Data.java new file mode 100644 index 00000000..df451e02 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Data.java @@ -0,0 +1,90 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Maps; +import io.getstream.core.models.serialization.DataDeserializer; + +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +@JsonDeserialize(using = DataDeserializer.class) +public final class Data { + private final String id; + private final Map data = Maps.newHashMap(); + + public Data(String id) { + this.id = checkNotNull(id, "ID required"); + } + + public Data() { + this(""); + } + + public static Data buildFrom(T data) { + return convert(data, Data.class); + } + + public String getID() { + return id; + } + + @JsonAnyGetter + public Map getData() { + return data; + } + + public Data set(String key, T value) { + checkArgument(!"id".equals(key), "Key can't be named 'id'"); + checkNotNull(key, "Key can't be null"); + checkNotNull(value, "Value can't be null"); + + data.put(key, value); + return this; + } + + public Data from(T data) { + return from(convert(data, new TypeReference>() {})); + } + + public Data from(Map map) { + checkNotNull(data, "Can't extract data from null"); + + for (Map.Entry entry : map.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + return this; + } + + public T get(String key) { + return (T) data.get(checkNotNull(key, "Key can't be null")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Data data = (Data) o; + return Objects.equals(id, data.id) && + Objects.equals(data, data.data); + } + + @Override + public int hashCode() { + return Objects.hash(id, data); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("data", this.data) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Engagement.java b/src/main/java/io/getstream/core/models/Engagement.java new file mode 100644 index 00000000..faace27a --- /dev/null +++ b/src/main/java/io/getstream/core/models/Engagement.java @@ -0,0 +1,210 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Engagement.Builder.class) +public class Engagement { + private final String feedID; + private final UserData userData; + private final String label; + private final Content content; + private final Integer boost; + private final Integer position; + private final String location; + private final List features; + private final Date trackedAt; + + private Engagement(Builder builder) { + label = builder.label; + content = builder.content; + boost = builder.boost; + position = builder.position; + feedID = builder.feedID; + location = builder.location; + userData = builder.userData; + features = builder.features; + trackedAt = builder.trackedAt; + } + + public static Builder builder() { + return new Builder(); + } + + public String getLabel() { + return label; + } + + public Content getContent() { + return content; + } + + public int getBoost() { + return boost; + } + + public int getPosition() { + return position; + } + + @JsonProperty("feed_id") + public String getFeedID() { + return feedID; + } + + public String getLocation() { + return location; + } + + @JsonProperty("user_data") + public UserData getUserData() { + return userData; + } + + public List getFeatures() { + return features; + } + + @JsonProperty("tracked_at") + public Date getTrackedAt() { + return trackedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Engagement that = (Engagement) o; + return Objects.equals(label, that.label) && + Objects.equals(content, that.content) && + Objects.equals(boost, that.boost) && + Objects.equals(position, that.position) && + Objects.equals(feedID, that.feedID) && + Objects.equals(location, that.location) && + Objects.equals(userData, that.userData) && + Objects.equals(features, that.features) && + Objects.equals(trackedAt, that.trackedAt); + } + + @Override + public int hashCode() { + return Objects.hash(label, content, boost, position, feedID, location, userData, features, trackedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("label", this.label) + .add("content", this.content) + .add("boost", this.boost) + .add("position", this.position) + .add("feedID", this.feedID) + .add("location", this.location) + .add("userData", this.userData) + .add("features", this.features) + .add("trackedAt", this.trackedAt) + .toString(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String label; + private Content content; + private Integer boost; + private Integer position; + private String feedID; + private String location; + private UserData userData; + private List features; + private Date trackedAt; + + public Builder label(String label) { + this.label = label; + return this; + } + + public Builder content(Content content) { + this.content = content; + return this; + } + + public Builder boost(int boost) { + this.boost = boost; + return this; + } + + public Builder position(int position) { + this.position = position; + return this; + } + + @JsonProperty("feed_id") + public Builder feedID(String feedID) { + this.feedID = feedID; + return this; + } + + public Builder location(String location) { + this.location = location; + return this; + } + + @JsonProperty("user_data") + public Builder userData(UserData userData) { + this.userData = userData; + return this; + } + + public Builder features(List features) { + this.features = features; + return this; + } + + @JsonProperty("tracked_at") + public Builder trackedAt(Date trackedAt) { + this.trackedAt = trackedAt; + return this; + } + + @JsonIgnore + public Builder fromEngagement(Engagement engagement) { + label = engagement.label; + content = engagement.content; + boost = engagement.boost; + position = engagement.position; + feedID = engagement.feedID; + location = engagement.location; + userData = engagement.userData; + features = engagement.features; + trackedAt = engagement.trackedAt; + return this; + } + + @JsonIgnore + public Builder fromCustomEngagement(T custom) { + return fromEngagement(convert(custom, Engagement.class)); + } + + public Engagement build() { + checkNotNull(feedID, "Engagement 'feedID' field required"); + checkNotNull(userData, "Engagement 'userData' field required"); + checkNotNull(label, "Engagement 'label' field required"); + checkNotNull(content, "Engagement 'content' field required"); + + return new Engagement(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/EnrichedActivity.java b/src/main/java/io/getstream/core/models/EnrichedActivity.java new file mode 100644 index 00000000..ccd4bc89 --- /dev/null +++ b/src/main/java/io/getstream/core/models/EnrichedActivity.java @@ -0,0 +1,333 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = EnrichedActivity.Builder.class) +public class EnrichedActivity { + private final String id; + private final Data actor; + private final Data verb; + private final Data object; + private final String foreignID; + private final Data target; + //TODO: support Java 8 Date/Time types? + private final Date time; + private final Data origin; + private final List to; + private final Double score; + private final Map reactionCounts; + private final Map> ownReactions; + private final Map> latestReactions; + private final Map extra; + + private EnrichedActivity(Builder builder) { + id = builder.id; + actor = builder.actor; + verb = builder.verb; + object = builder.object; + foreignID = builder.foreignID; + target = builder.target; + time = builder.time; + origin = builder.origin; + to = builder.to; + score = builder.score; + reactionCounts = builder.reactionCounts; + ownReactions = builder.ownReactions; + latestReactions = builder.latestReactions; + extra = builder.extra; + } + + public String getID() { + return id; + } + + public Data getActor() { + return actor; + } + + public Data getVerb() { + return verb; + } + + public Data getObject() { + return object; + } + + @JsonProperty("foreign_id") + public String getForeignID() { + return foreignID; + } + + public Data getTarget() { + return target; + } + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public Date getTime() { + return time; + } + + public Data getOrigin() { + return origin; + } + + public List getTo() { + return to; + } + + public Double getScore() { + return score; + } + + @JsonAnyGetter + public Map getExtra() { + return extra; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EnrichedActivity activity = (EnrichedActivity) o; + return Objects.equals(id, activity.id) && + Objects.equals(actor, activity.actor) && + Objects.equals(verb, activity.verb) && + Objects.equals(object, activity.object) && + Objects.equals(foreignID, activity.foreignID) && + Objects.equals(target, activity.target) && + Objects.equals(time, activity.time) && + Objects.equals(origin, activity.origin) && + Objects.equals(to, activity.to) && + Objects.equals(score, activity.score) && + Objects.equals(extra, activity.extra); + } + + @Override + public int hashCode() { + return Objects.hash(id, actor, verb, object, foreignID, target, time, origin, to, score, extra); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("id", this.id) + .add("actor", this.actor) + .add("verb", this.verb) + .add("object", this.object) + .add("foreignID", this.foreignID) + .add("target", this.target) + .add("time", this.time) + .add("origin", this.origin) + .add("to", this.to) + .add("score", this.score) + .add("ownReactions", this.ownReactions) + .add("latestReactions", this.latestReactions) + .add("reactionCounts", this.reactionCounts) + .add("extra", this.extra) + .toString(); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String id; + private Data actor; + private Data verb; + private Data object; + private String foreignID; + private Data target; + private Date time; + private Data origin; + private List to; + private Double score; + private Map reactionCounts; + private Map> ownReactions; + private Map> latestReactions; + private Map extra; + + public Builder id(String id) { + this.id = id; + return this; + } + + @JsonIgnore + public Builder actor(String actor) { + this.actor = new Data(actor); + return this; + } + + @JsonProperty("actor") + public Builder actor(Data actor) { + this.actor = actor; + return this; + } + + @JsonIgnore + public Builder verb(String verb) { + this.verb = new Data(verb); + return this; + } + + @JsonProperty("verb") + public Builder verb(Data verb) { + this.verb = verb; + return this; + } + + @JsonIgnore + public Builder object(String object) { + this.object = new Data(object); + return this; + } + + @JsonProperty("object") + public Builder object(Data object) { + this.object = object; + return this; + } + + @JsonProperty("foreign_id") + public Builder foreignID(String foreignID) { + this.foreignID = foreignID; + return this; + } + + @JsonIgnore + public Builder target(String target) { + this.target = new Data(target); + return this; + } + + @JsonProperty("target") + public Builder target(Data target) { + this.target = target; + return this; + } + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + public Builder time(Date time) { + this.time = time; + return this; + } + + @JsonIgnore + public Builder origin(String origin) { + this.origin = new Data(origin); + return this; + } + + @JsonProperty("origin") + public Builder origin(Data origin) { + this.origin = origin; + return this; + } + + @JsonProperty("to") + public Builder to(List to) { + this.to = to; + return this; + } + + @JsonIgnore + public Builder to(Iterable to) { + this.to = Lists.newArrayList(to); + return this; + } + + @JsonIgnore + public Builder to(FeedID... to) { + this.to = Lists.newArrayList(to); + return this; + } + + public Builder score(double score) { + this.score = score; + return this; + } + + @JsonProperty("own_reactions") + public Builder ownReactions(Map> ownReactions) { + this.ownReactions = ownReactions; + return this; + } + + @JsonProperty("latest_reactions") + public Builder latestReactions(Map> latestReactions) { + this.latestReactions = latestReactions; + return this; + } + + @JsonProperty("reaction_counts") + public Builder reactionCounts(Map reactionCounts) { + this.reactionCounts = reactionCounts; + return this; + } + + @JsonAnySetter + public Builder extraField(String key, Object value) { + if (extra == null) { + extra = Maps.newHashMap(); + } + extra.put(key, value); + return this; + } + + @JsonIgnore + public Builder extra(Map extra) { + if (!extra.isEmpty()) { + this.extra = extra; + } + return this; + } + + @JsonIgnore + public Builder fromEnrichedActivity(EnrichedActivity activity) { + this.id = activity.id; + this.actor = activity.actor; + this.verb = activity.verb; + this.object = activity.object; + this.foreignID = activity.foreignID; + this.target = activity.target; + this.time = activity.time; + this.origin = activity.origin; + this.to = activity.to; + this.score = activity.score; + this.ownReactions = activity.ownReactions; + this.latestReactions = activity.latestReactions; + this.reactionCounts = activity.reactionCounts; + this.extra = activity.extra; + return this; + } + + @JsonIgnore + public Builder fromCustomEnrichedActivity(T custom) { + return fromEnrichedActivity(convert(custom, EnrichedActivity.class)); + } + + public EnrichedActivity build() { + checkNotNull(actor, "EnrichedActivity 'actor' field required"); + checkNotNull(verb, "EnrichedActivity 'verb' field required"); + checkNotNull(object, "EnrichedActivity 'object' field required"); + + return new EnrichedActivity(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/Feature.java b/src/main/java/io/getstream/core/models/Feature.java new file mode 100644 index 00000000..ef7d3481 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Feature.java @@ -0,0 +1,48 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + +import java.util.Objects; + +public final class Feature { + private final String group; + private final String value; + + @JsonCreator + public Feature(@JsonProperty("group") String group, @JsonProperty("value") String value) { + this.group = group; + this.value = value; + } + + public String getGroup() { + return group; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Feature feature = (Feature) o; + return Objects.equals(group, feature.group) && + Objects.equals(value, feature.value); + } + + @Override + public int hashCode() { + return Objects.hash(group, value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("group", this.group) + .add("value", this.value) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/FeedID.java b/src/main/java/io/getstream/core/models/FeedID.java new file mode 100644 index 00000000..680609ca --- /dev/null +++ b/src/main/java/io/getstream/core/models/FeedID.java @@ -0,0 +1,62 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +@JsonSerialize(using = ToStringSerializer.class) +public final class FeedID { + private final String slug; + private final String userID; + + public FeedID(String slug, String userID) { + checkNotNull(slug, "Feed slug can't be null"); + checkArgument(!slug.contains(":"), "Invalid slug"); + checkNotNull(userID, "Feed user ID can't be null"); + checkArgument(!userID.contains(":"), "Invalid user ID"); + + this.slug = slug; + this.userID = userID; + } + + public FeedID(String id) { + checkNotNull(id, "Feed ID can't be null"); + checkArgument(id.contains(":"), "Invalid feed ID"); + + String[] parts = id.split(":"); + checkArgument(parts.length == 2, "Invalid feed ID"); + this.slug = parts[0]; + this.userID = parts[1]; + } + + public String getSlug() { + return slug; + } + + public String getUserID() { + return userID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FeedID feedID = (FeedID) o; + return Objects.equals(slug, feedID.slug) && + Objects.equals(userID, feedID.userID); + } + + @Override + public int hashCode() { + return Objects.hash(slug, userID); + } + + @Override + public String toString() { + return slug + ':' + userID; + } +} diff --git a/src/main/java/io/getstream/core/models/FollowRelation.java b/src/main/java/io/getstream/core/models/FollowRelation.java new file mode 100644 index 00000000..6383b5cc --- /dev/null +++ b/src/main/java/io/getstream/core/models/FollowRelation.java @@ -0,0 +1,55 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +@JsonIgnoreProperties(ignoreUnknown = true) +public final class FollowRelation { + private final String source; + private final String target; + + @JsonCreator + public FollowRelation(@JsonProperty("feed_id") String source, @JsonProperty("target_id") String target) { + checkNotNull(source, "FollowRelation 'source' field required"); + checkNotNull(target, "FollowRelation 'target' field required"); + + this.source = source; + this.target = target; + } + + public String getSource() { + return this.source; + } + + public String getTarget() { + return this.target; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FollowRelation that = (FollowRelation) o; + return Objects.equals(source, that.source) && + Objects.equals(target, that.target); + } + + @Override + public int hashCode() { + return Objects.hash(source, target); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("source", this.source) + .add("target", this.target) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/ForeignIDTimePair.java b/src/main/java/io/getstream/core/models/ForeignIDTimePair.java new file mode 100644 index 00000000..0d8b65fa --- /dev/null +++ b/src/main/java/io/getstream/core/models/ForeignIDTimePair.java @@ -0,0 +1,46 @@ +package io.getstream.core.models; + +import com.google.common.base.MoreObjects; + +import java.util.Date; +import java.util.Objects; + +public final class ForeignIDTimePair { + private final String foreignID; + private final Date time; + + public ForeignIDTimePair(String foreignID, Date time) { + this.foreignID = foreignID; + this.time = time; + } + + public String getForeignID() { + return foreignID; + } + + public Date getTime() { + return time; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ForeignIDTimePair that = (ForeignIDTimePair) o; + return Objects.equals(foreignID, that.foreignID) && + Objects.equals(time, that.time); + } + + @Override + public int hashCode() { + return Objects.hash(foreignID, time); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("foreignID", this.foreignID) + .add("time", this.time) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Group.java b/src/main/java/io/getstream/core/models/Group.java new file mode 100644 index 00000000..25679ee4 --- /dev/null +++ b/src/main/java/io/getstream/core/models/Group.java @@ -0,0 +1,93 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Group { + private final String group; + private final List activities; + private final int actorCount; + private final Date createdAt; + private final Date updatedAt; + + @JsonCreator + public Group( + @JsonProperty("group") + String group, + @JsonProperty("activities") + List activities, + @JsonProperty("actor_count") + int actorCount, + @JsonProperty("created_at") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + Date createdAt, + @JsonProperty("updated_at") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + Date updatedAt) { + checkNotNull(group, "Group 'group' field required"); + checkNotNull(activities, "Group 'activities' field required"); + + this.group = group; + this.activities = activities; + this.actorCount = actorCount; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public String getGroup() { + return group; + } + + public List getActivities() { + return activities; + } + + public int getActorCount() { + return actorCount; + } + + public Date getCreatedAt() { + return createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Group that = (Group) o; + return actorCount == that.actorCount && + Objects.equals(group, that.group) && + Objects.equals(activities, that.activities) && + Objects.equals(createdAt, that.createdAt) && + Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(group, activities, actorCount, createdAt, updatedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("activities", this.activities) + .add("actorCount", this.actorCount) + .add("createdAt", this.createdAt) + .add("updatedAt", this.updatedAt) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/Impression.java b/src/main/java/io/getstream/core/models/Impression.java new file mode 100644 index 00000000..8e95841c --- /dev/null +++ b/src/main/java/io/getstream/core/models/Impression.java @@ -0,0 +1,193 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.getstream.core.utils.Serialization.convert; + +@JsonInclude(Include.NON_NULL) +@JsonDeserialize(builder = Impression.Builder.class) +public class Impression { + private final String feedID; + private final UserData userData; + private final List contentList; + private final String position; + private final String location; + private final List features; + private final Date trackedAt; + + private Impression(Builder builder) { + position = builder.position; + feedID = builder.feedID; + location = builder.location; + userData = builder.userData; + contentList = builder.contentList; + features = builder.features; + trackedAt = builder.trackedAt; + } + + public static Builder builder() { + return new Builder(); + } + + public String getPosition() { + return position; + } + + @JsonProperty("feed_id") + public String getFeedID() { + return feedID; + } + + public String getLocation() { + return location; + } + + @JsonProperty("user_data") + public UserData getUserData() { + return userData; + } + + @JsonProperty("content_list") + public List getContentList() { + return contentList; + } + + public List getFeatures() { + return features; + } + + @JsonProperty("tracked_at") + public Date getTrackedAt() { + return trackedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Impression that = (Impression) o; + return Objects.equals(position, that.position) && + Objects.equals(feedID, that.feedID) && + Objects.equals(location, that.location) && + Objects.equals(userData, that.userData) && + Objects.equals(contentList, that.contentList) && + Objects.equals(features, that.features) && + Objects.equals(trackedAt, that.trackedAt); + } + + @Override + public int hashCode() { + return Objects.hash(position, feedID, location, userData, contentList, features, trackedAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("position", this.position) + .add("feedID", this.feedID) + .add("location", this.location) + .add("userData", this.userData) + .add("contentList", this.contentList) + .add("features", this.features) + .add("trackedAt", this.trackedAt) + .toString(); + } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder { + private String position; + private String feedID; + private String location; + private UserData userData; + private List contentList; + private List features; + private Date trackedAt; + + public Builder position(String position) { + this.position = position; + return this; + } + + @JsonProperty("feed_id") + public Builder feedID(String feedID) { + this.feedID = feedID; + return this; + } + + public Builder location(String location) { + this.location = location; + return this; + } + + @JsonProperty("user_data") + public Builder userData(UserData userData) { + this.userData = userData; + return this; + } + + @JsonProperty("content_list") + public Builder contentList(List contentList) { + this.contentList = contentList; + return this; + } + + @JsonIgnore + public Builder contentList(Iterable contentList) { + this.contentList = Lists.newArrayList(contentList); + return this; + } + + @JsonIgnore + public Builder contentList(Content... contentList) { + this.contentList = Lists.newArrayList(contentList); + return this; + } + + public Builder features(List features) { + this.features = features; + return this; + } + + @JsonProperty("tracked_at") + public Builder trackedAt(Date trackedAt) { + this.trackedAt = trackedAt; + return this; + } + + @JsonIgnore + public Builder fromImpression(Impression impression) { + position = impression.position; + feedID = impression.feedID; + location = impression.location; + userData = impression.userData; + contentList = impression.contentList; + features = impression.features; + trackedAt = impression.trackedAt; + return this; + } + + @JsonIgnore + public Builder fromCustomImpression(T custom) { + return fromImpression(convert(custom, Impression.class)); + } + + public Impression build() { + checkNotNull(feedID, "Impression 'feedID' field required"); + checkNotNull(userData, "Impression 'userData' field required"); + + return new Impression(this); + } + } +} diff --git a/src/main/java/io/getstream/core/models/NotificationGroup.java b/src/main/java/io/getstream/core/models/NotificationGroup.java new file mode 100644 index 00000000..7c47ada5 --- /dev/null +++ b/src/main/java/io/getstream/core/models/NotificationGroup.java @@ -0,0 +1,76 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; + +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class NotificationGroup extends Group { + private final boolean seen; + private final boolean read; + + @JsonCreator + public NotificationGroup( + @JsonProperty("group") + String group, + @JsonProperty("activities") + List activities, + @JsonProperty("actor_count") + int actorCount, + @JsonProperty("created_at") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + Date createdAt, + @JsonProperty("updated_at") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", timezone = "UTC") + Date updatedAt, + @JsonProperty("is_seen") + boolean isSeen, + @JsonProperty("is_read") + boolean isRead) { + super(group, activities, actorCount, createdAt, updatedAt); + + this.seen = isSeen; + this.read = isRead; + } + + public boolean isSeen() { + return seen; + } + + public boolean isRead() { + return read; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + NotificationGroup that = (NotificationGroup) o; + return seen == that.seen && + read == that.read; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), seen, read); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("activities", getActivities()) + .add("actorCount", getActorCount()) + .add("createdAt", getCreatedAt()) + .add("updatedAt", getUpdatedAt()) + .add("isSeen", seen) + .add("isRead", read) + .toString(); + } +} diff --git a/src/main/java/io/getstream/core/models/OGData.java b/src/main/java/io/getstream/core/models/OGData.java new file mode 100644 index 00000000..edb478ff --- /dev/null +++ b/src/main/java/io/getstream/core/models/OGData.java @@ -0,0 +1,268 @@ +package io.getstream.core.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class OGData { + public static class Image { + private final String image; + private final String url; + private final String secureUrl; + private final String width; + private final String height; + private final String type; + private final String alt; + + @JsonCreator + public Image( + @JsonProperty("image") + String image, + @JsonProperty("url") + String url, + @JsonProperty("secure_url") + String secureUrl, + @JsonProperty("width") + String width, + @JsonProperty("height") + String height, + @JsonProperty("type") + String type, + @JsonProperty("alt") + String alt) { + this.image = image; + this.url = url; + this.secureUrl = secureUrl; + this.width = width; + this.height = height; + this.type = type; + this.alt = alt; + } + + public String getImage() { + return image; + } + + public String getURL() { + return url; + } + + public String getSecureUrl() { + return secureUrl; + } + + public String getWidth() { + return width; + } + + public String getHeight() { + return height; + } + + public String getType() { + return type; + } + + public String getAlt() { + return alt; + } + } + + public static class Video { + private final String video; + private final String alt; + private final String url; + private final String secureURL; + private final String type; + private final String width; + private final String height; + + @JsonCreator + public Video( + @JsonProperty("video") + String video, + @JsonProperty("alt") + String alt, + @JsonProperty("url") + String url, + @JsonProperty("secure_url") + String secureURL, + @JsonProperty("type") + String type, + @JsonProperty("width") + String width, + @JsonProperty("height") + String height) { + this.video = video; + this.alt = alt; + this.url = url; + this.secureURL = secureURL; + this.type = type; + this.width = width; + this.height = height; + } + + public String getSecureURL() { + return secureURL; + } + + public String getURL(){ + return url; + } + + public String getWidth() { + return width; + } + + public String getHeight() { + return height; + } + + public String getType() { + return type; + } + + public String getAlt() { + return alt; + } + + public String getVideo() { + return video; + } + } + + public static class Audio { + private final String url; + private final String secureURL; + private final String type; + private final String audio; + + @JsonCreator + public Audio( + @JsonProperty("url") + String url, + @JsonProperty("secure_url") + String secureURL, + @JsonProperty("type") + String type, + @JsonProperty("audio") + String audio) { + this.type = type; + this.audio = audio; + this.url = url; + this.secureURL = secureURL; + } + + public String getSecureURL() { + return secureURL; + } + + public String getURL(){ + return url; + } + + public String getType() { + return type; + } + + public String getAudio() { + return audio; + } + } + + private final String title; + private final String type; + private final String description; + private final String determiner; + private final String locale; + private final String siteName; + private final List images; + private final List