From 69fd92d9e9be028041bb9c6329a1935ff5e09402 Mon Sep 17 00:00:00 2001 From: Yatharth Ranjan Date: Thu, 4 Mar 2021 12:29:38 +0000 Subject: [PATCH 01/23] Snapshot version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e7f408b0..7e1ca57e 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ subprojects { apply plugin: 'java-library' group = 'org.radarbase' - version = '0.3.3' + version = '0.3.4-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From a7f576fa2d88007a651675d900c9c3f9a08d38b2 Mon Sep 17 00:00:00 2001 From: mpgxvii Date: Fri, 5 Mar 2021 23:44:11 +0800 Subject: [PATCH 02/23] Set connect timeout and read timeout for the OkHttpClient in the service user repo --- .../connect/rest/fitbit/user/ServiceUserRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 626c5562..69527baf 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -61,6 +61,7 @@ public class ServiceUserRepository implements UserRepository { private static final RequestBody EMPTY_BODY = RequestBody.create("", MediaType.parse("application/json; charset=utf-8")); private static final Duration FETCH_THRESHOLD = Duration.ofMinutes(1L); + private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(30); private final OkHttpClient client; private final Map cachedCredentials; @@ -73,7 +74,7 @@ public class ServiceUserRepository implements UserRepository { private String basicCredentials; public ServiceUserRepository() { - this.client = new OkHttpClient(); + this.client = new OkHttpClient.Builder().connectTimeout(CONNECTION_TIMEOUT).readTimeout(CONNECTION_TIMEOUT).build(); this.cachedCredentials = new HashMap<>(); this.containedUsers = new HashSet<>(); } @@ -115,7 +116,7 @@ public Stream stream() { try { applyPendingUpdates(); } catch (IOException ex) { - logger.error("Failed to initially get users from repository"); + logger.error("Failed to initially get users from repository", ex); } } return this.timedCachedUsers.stream(); From 8002fa35d55bba13348ee8e403fdb685d80b2b56 Mon Sep 17 00:00:00 2001 From: mpgxvii Date: Sat, 6 Mar 2021 01:17:22 +0800 Subject: [PATCH 03/23] Separate connection timeout and read timeout values --- .../connect/rest/fitbit/user/ServiceUserRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 69527baf..53fdfa9e 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -61,7 +61,8 @@ public class ServiceUserRepository implements UserRepository { private static final RequestBody EMPTY_BODY = RequestBody.create("", MediaType.parse("application/json; charset=utf-8")); private static final Duration FETCH_THRESHOLD = Duration.ofMinutes(1L); - private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(30); + private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(60); + private static final Duration CONNECTION_READ_TIMEOUT = Duration.ofSeconds(60); private final OkHttpClient client; private final Map cachedCredentials; @@ -74,7 +75,7 @@ public class ServiceUserRepository implements UserRepository { private String basicCredentials; public ServiceUserRepository() { - this.client = new OkHttpClient.Builder().connectTimeout(CONNECTION_TIMEOUT).readTimeout(CONNECTION_TIMEOUT).build(); + this.client = new OkHttpClient.Builder().connectTimeout(CONNECTION_TIMEOUT).readTimeout(CONNECTION_READ_TIMEOUT).build(); this.cachedCredentials = new HashMap<>(); this.containedUsers = new HashSet<>(); } From 902b265777a0f037a70e44061d6c5a8ee4873ae7 Mon Sep 17 00:00:00 2001 From: mpgxvii Date: Sat, 6 Mar 2021 02:27:51 +0800 Subject: [PATCH 04/23] Improve readability and increase connection timeout --- .../connect/rest/fitbit/user/ServiceUserRepository.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 53fdfa9e..2d2551a7 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -62,7 +62,7 @@ public class ServiceUserRepository implements UserRepository { RequestBody.create("", MediaType.parse("application/json; charset=utf-8")); private static final Duration FETCH_THRESHOLD = Duration.ofMinutes(1L); private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(60); - private static final Duration CONNECTION_READ_TIMEOUT = Duration.ofSeconds(60); + private static final Duration CONNECTION_READ_TIMEOUT = Duration.ofSeconds(90); private final OkHttpClient client; private final Map cachedCredentials; @@ -75,7 +75,10 @@ public class ServiceUserRepository implements UserRepository { private String basicCredentials; public ServiceUserRepository() { - this.client = new OkHttpClient.Builder().connectTimeout(CONNECTION_TIMEOUT).readTimeout(CONNECTION_READ_TIMEOUT).build(); + this.client = new OkHttpClient.Builder() + .connectTimeout(CONNECTION_TIMEOUT) + .readTimeout(CONNECTION_READ_TIMEOUT) + .build(); this.cachedCredentials = new HashMap<>(); this.containedUsers = new HashSet<>(); } From 7a5a1c6c0457d2194fd358b64f31608c5a8ce959 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 14 Jul 2021 10:38:58 +0200 Subject: [PATCH 05/23] Updated Gradle --- build.gradle | 5 +---- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- kafka-connect-fitbit-source/build.gradle | 4 ++-- .../fitbit/user/ServiceUserRepository.java | 6 +++--- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 7e1ca57e..f13a4817 100644 --- a/build.gradle +++ b/build.gradle @@ -20,14 +20,11 @@ subprojects { mavenCentral() maven { url "https://packages.confluent.io/maven/" } maven { url "https://repo.maven.apache.org/maven2" } - jcenter() - maven { url "https://dl.bintray.com/radar-cns/org.radarcns" } - maven { url 'https://oss.jfrog.org/artifactory/oss-snapshot-local/' } } } wrapper { - gradleVersion '6.6.1' + gradleVersion '7.1.1' } evaluationDependsOnChildren() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 12d38de6..05679dc3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c..744e882e 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/kafka-connect-fitbit-source/build.gradle b/kafka-connect-fitbit-source/build.gradle index 79071e29..2f0dab12 100644 --- a/kafka-connect-fitbit-source/build.gradle +++ b/kafka-connect-fitbit-source/build.gradle @@ -1,9 +1,9 @@ dependencies { api project(':kafka-connect-rest-source') api group: 'io.confluent', name: 'kafka-connect-avro-converter', version: confluentVersion - api group: 'org.radarcns', name: 'radar-schemas-commons', version: '0.5.14' + api group: 'org.radarbase', name: 'radar-schemas-commons', version: '0.7.1' - implementation group: 'org.radarcns', name: 'oauth-client-util', version: '0.6.0' + implementation group: 'org.radarbase', name: 'oauth-client-util', version: '0.7.1' implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: jacksonVersion implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: jacksonVersion diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 2d2551a7..691ad9bc 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -46,8 +46,8 @@ import okhttp3.ResponseBody; import org.radarbase.connect.rest.RestSourceConnectorConfig; import org.radarbase.connect.rest.fitbit.FitbitRestSourceConnectorConfig; -import org.radarcns.exception.TokenException; -import org.radarcns.oauth.OAuth2Client; +import org.radarbase.exception.TokenException; +import org.radarbase.oauth.OAuth2Client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -214,7 +214,7 @@ private T makeRequest(Request request, ObjectReader reader) throws IOExcepti try { return reader.readValue(bodyString); } catch (JsonProcessingException ex) { - logger.error("Failed to parse JSON: {}\n{}", ex.toString(), bodyString); + logger.error("Failed to parse JSON: {}\n{}", ex, bodyString); throw ex; } } From 2a308702cfea9c9b1e4ac5d6f9c2986aca14e4c4 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 14 Jul 2021 10:56:59 +0200 Subject: [PATCH 06/23] Updated dependencies --- build.gradle | 45 ++++++++++++++----- kafka-connect-fitbit-source/build.gradle | 8 ++-- .../fitbit/user/ServiceUserRepository.java | 2 +- kafka-connect-rest-source/build.gradle | 10 ++--- .../radarbase/connect/rest/RestTaskTest.java | 18 ++++---- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index f13a4817..571eea98 100644 --- a/build.gradle +++ b/build.gradle @@ -1,26 +1,34 @@ +import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask + +plugins { + id("com.github.ben-manes.versions") version "0.39.0" +} + description = 'kafka-connect-rest-source' +allprojects { + group = "org.radarbase" + version = "3.2.0" + + repositories { + mavenCentral() + maven { url "https://packages.confluent.io/maven/" } + maven { url "https://repo.maven.apache.org/maven2" } + } +} + subprojects { ext { - kafkaVersion = '2.5.1' - confluentVersion = '5.5.2' - jacksonVersion = '2.11.3' + kafkaVersion = '2.8.0' + confluentVersion = '6.2.0' + jacksonVersion = '2.12.4' } apply plugin: 'java' apply plugin: 'java-library' - group = 'org.radarbase' - version = '0.3.4-SNAPSHOT' - sourceCompatibility = 1.8 targetCompatibility = 1.8 - - repositories { - mavenCentral() - maven { url "https://packages.confluent.io/maven/" } - maven { url "https://repo.maven.apache.org/maven2" } - } } wrapper { @@ -39,3 +47,16 @@ task downloadDependencies { println 'Downloaded REST code dependencies' } } + + +def isNonStable = { String version -> + def stableKeyword = ["RELEASE", "FINAL", "GA"].any { version.toUpperCase().contains(it) } + def regex = /^[0-9,.v-]+(-r)?$/ + return !stableKeyword && !(version ==~ regex) +} + +tasks.named("dependencyUpdates").configure { + rejectVersionIf { + isNonStable(it.candidate.version) + } +} diff --git a/kafka-connect-fitbit-source/build.gradle b/kafka-connect-fitbit-source/build.gradle index 2f0dab12..a0cfab64 100644 --- a/kafka-connect-fitbit-source/build.gradle +++ b/kafka-connect-fitbit-source/build.gradle @@ -7,15 +7,15 @@ dependencies { implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: jacksonVersion implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: jacksonVersion - implementation 'com.google.firebase:firebase-admin:6.16.0' + implementation 'com.google.firebase:firebase-admin:8.0.0' // Included in connector runtime compileOnly group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.2' - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.6.2' - testRuntimeOnly group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.2' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.2' + testRuntimeOnly group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.31' testImplementation group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion } diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 691ad9bc..8425efbd 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; -import io.confluent.common.config.ConfigException; import java.io.IOException; import java.net.URL; import java.time.Duration; @@ -44,6 +43,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import org.apache.kafka.common.config.ConfigException; import org.radarbase.connect.rest.RestSourceConnectorConfig; import org.radarbase.connect.rest.fitbit.FitbitRestSourceConnectorConfig; import org.radarbase.exception.TokenException; diff --git a/kafka-connect-rest-source/build.gradle b/kafka-connect-rest-source/build.gradle index 97c085e8..92282fab 100644 --- a/kafka-connect-rest-source/build.gradle +++ b/kafka-connect-rest-source/build.gradle @@ -1,14 +1,14 @@ dependencies { - api group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.0' + api group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.1' // Included in connector runtime compileOnly group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.2' - testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.6.2' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '2.27.0' - testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.23.2' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.2' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.2' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.11.2' + testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.27.2' testImplementation group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion } diff --git a/kafka-connect-rest-source/src/test/java/org/radarbase/connect/rest/RestTaskTest.java b/kafka-connect-rest-source/src/test/java/org/radarbase/connect/rest/RestTaskTest.java index ef5f8d37..d4868e6a 100644 --- a/kafka-connect-rest-source/src/test/java/org/radarbase/connect/rest/RestTaskTest.java +++ b/kafka-connect-rest-source/src/test/java/org/radarbase/connect/rest/RestTaskTest.java @@ -28,7 +28,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.VerificationException; @@ -118,10 +118,10 @@ public OffsetStorageReader offsetStorageReader() { sourceTask.start(props); messages = sourceTask.poll(); - assertEquals("Message count: ", 1, messages.size()); - assertEquals("Response class: ", String.class, messages.get(0).value().getClass()); - assertEquals("Response body: ", RESPONSE_BODY, messages.get(0).value()); - assertEquals("Topic: ", TOPIC, messages.get(0).topic()); + assertEquals(1, messages.size(), "Message count: "); + assertEquals(String.class, messages.get(0).value().getClass(), "Response class: "); + assertEquals(RESPONSE_BODY, messages.get(0).value(), "Response body: "); + assertEquals(TOPIC, messages.get(0).topic(), "Topic: "); verify(postRequestedFor(urlMatching(PATH)) .withRequestBody(equalTo(DATA)) @@ -134,10 +134,10 @@ public OffsetStorageReader offsetStorageReader() { sourceTask.start(props); messages = sourceTask.poll(); - assertEquals("Message count: ", 1, messages.size()); - assertEquals("Response class: ", byte[].class, messages.get(0).value().getClass()); - assertEquals("Response body: ", RESPONSE_BODY, new String((byte[]) messages.get(0).value())); - assertEquals("Topic: ", TOPIC, messages.get(0).topic()); + assertEquals(1, messages.size(), "Message count: "); + assertEquals(byte[].class, messages.get(0).value().getClass(), "Response class: "); + assertEquals(RESPONSE_BODY, new String((byte[]) messages.get(0).value()), "Response body: "); + assertEquals(TOPIC, messages.get(0).topic(), "Topic: "); verify(postRequestedFor(urlMatching(PATH)) .withRequestBody(equalTo(DATA)) From 24b0da911ad935bb47f606b0ac5a2fdae79deec6 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 16:17:37 +0200 Subject: [PATCH 07/23] Do not retry failed authorization within a call --- .../connect/rest/fitbit/user/ServiceUserRepository.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 8425efbd..db5d9f44 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -131,7 +131,7 @@ public String getAccessToken(User user) throws IOException, NotAuthorizedExcepti OAuth2UserCredentials credentials = cachedCredentials.get(user.getId()); if (credentials == null || credentials.isAccessTokenExpired()) { Request request = requestFor("users/" + user.getId() + "/token").build(); - credentials = makeRequest(request, OAUTH_READER); + credentials = makeRequest(request, OAUTH_READER);: cachedCredentials.put(user.getId(), credentials); } return credentials.getAccessToken(); @@ -200,6 +200,8 @@ private T makeRequest(Request request, ObjectReader reader) throws IOExcepti if (response.code() == 404) { throw new NoSuchElementException("URL " + request.url() + " does not exist"); + } else if (response.code() == 407) { + throw new NotAuthorizedException("Refresh token cannot be retrieved for unauthorized user"); } else if (!response.isSuccessful() || body == null) { String message = "Failed to make request"; if (response.code() > 0) { From 78ae4cba8bbac7ecd5ae094d2607979a37a7f22c Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 16:34:23 +0200 Subject: [PATCH 08/23] More complete authorization checks --- .../connect/rest/fitbit/user/LocalUser.java | 4 ++ .../fitbit/user/ServiceUserRepository.java | 37 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/LocalUser.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/LocalUser.java index 0412d577..2bc3ed67 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/LocalUser.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/LocalUser.java @@ -95,6 +95,10 @@ public void setOauth2Credentials(OAuth2UserCredentials oauth2Credentials) { this.oauth2Credentials = oauth2Credentials; } + public void setIsAuthorized(Boolean isAuthorized) { + this.isAuthorized = isAuthorized; + } + @JsonSetter("fitbitUserId") public void setFitbitUserId(String id) { this.serviceUserId = id; diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index db5d9f44..5e61ce36 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -123,26 +123,49 @@ public Stream stream() { logger.error("Failed to initially get users from repository", ex); } } - return this.timedCachedUsers.stream(); + return this.timedCachedUsers.stream() + .filter(User::isComplete); } @Override public String getAccessToken(User user) throws IOException, NotAuthorizedException { + if (!user.isAuthorized()) { + throw new NotAuthorizedException("User is not authorized"); + } OAuth2UserCredentials credentials = cachedCredentials.get(user.getId()); if (credentials == null || credentials.isAccessTokenExpired()) { - Request request = requestFor("users/" + user.getId() + "/token").build(); - credentials = makeRequest(request, OAUTH_READER);: - cachedCredentials.put(user.getId(), credentials); + try { + Request request = requestFor("users/" + user.getId() + "/token").build(); + credentials = makeRequest(request, OAUTH_READER); + cachedCredentials.put(user.getId(), credentials); + } catch (NotAuthorizedException ex) { + cachedCredentials.remove(user.getId()); + if (user instanceof LocalUser) { + ((LocalUser) user).setIsAuthorized(false); + } + throw ex; + } } return credentials.getAccessToken(); } @Override public String refreshAccessToken(User user) throws IOException, NotAuthorizedException { + if (!user.isAuthorized()) { + throw new NotAuthorizedException("User is not authorized"); + } Request request = requestFor("users/" + user.getId() + "/token").post(EMPTY_BODY).build(); - OAuth2UserCredentials credentials = makeRequest(request, OAUTH_READER); - cachedCredentials.put(user.getId(), credentials); - return credentials.getAccessToken(); + try { + OAuth2UserCredentials credentials = makeRequest(request, OAUTH_READER); + cachedCredentials.put(user.getId(), credentials); + return credentials.getAccessToken(); + } catch (NotAuthorizedException ex) { + cachedCredentials.remove(user.getId()); + if (user instanceof LocalUser) { + ((LocalUser) user).setIsAuthorized(false); + } + throw ex; + } } @Override From 6cf096fc2b0263ff7c0510f2fafeb828a58b6801 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 16:41:59 +0200 Subject: [PATCH 09/23] Updated dependencies --- Dockerfile | 10 +++------- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- kafka-connect-fitbit-source/build.gradle | 8 ++++---- kafka-connect-rest-source/build.gradle | 4 ++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index f1f346a2..93cac6f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,16 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM openjdk:8-alpine as builder +FROM gradle:7.2-jdk8 as builder RUN mkdir /code WORKDIR /code -ENV GRADLE_OPTS -Dorg.gradle.daemon=false - -COPY ./gradle/wrapper /code/gradle/wrapper -COPY ./gradlew /code/ -RUN ./gradlew --version +ENV GRADLE_USER_HOME=/code/.gradlecache COPY ./build.gradle ./settings.gradle /code/ COPY kafka-connect-rest-source/build.gradle /code/kafka-connect-rest-source/ @@ -40,7 +36,7 @@ COPY ./kafka-connect-fitbit-source/src/ /code/kafka-connect-fitbit-source/src RUN ./gradlew jar -FROM confluentinc/cp-kafka-connect-base:5.5.2 +FROM confluentinc/cp-kafka-connect-base:6.2.0-3-ubi8 MAINTAINER Joris Borgdorff diff --git a/build.gradle b/build.gradle index 571eea98..69c21d08 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ subprojects { ext { kafkaVersion = '2.8.0' confluentVersion = '6.2.0' - jacksonVersion = '2.12.4' + jacksonVersion = '2.12.5' } apply plugin: 'java' @@ -32,7 +32,7 @@ subprojects { } wrapper { - gradleVersion '7.1.1' + gradleVersion '7.2' } evaluationDependsOnChildren() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05679dc3..ffed3a25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kafka-connect-fitbit-source/build.gradle b/kafka-connect-fitbit-source/build.gradle index a0cfab64..be07a1c0 100644 --- a/kafka-connect-fitbit-source/build.gradle +++ b/kafka-connect-fitbit-source/build.gradle @@ -1,13 +1,13 @@ dependencies { api project(':kafka-connect-rest-source') api group: 'io.confluent', name: 'kafka-connect-avro-converter', version: confluentVersion - api group: 'org.radarbase', name: 'radar-schemas-commons', version: '0.7.1' + api group: 'org.radarbase', name: 'radar-schemas-commons', version: '0.7.3' - implementation group: 'org.radarbase', name: 'oauth-client-util', version: '0.7.1' + implementation group: 'org.radarbase', name: 'oauth-client-util', version: '0.8.0' implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: jacksonVersion implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: jacksonVersion - implementation 'com.google.firebase:firebase-admin:8.0.0' + implementation 'com.google.firebase:firebase-admin:8.0.1' // Included in connector runtime compileOnly group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion @@ -15,7 +15,7 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.2' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.2' - testRuntimeOnly group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.31' + testRuntimeOnly group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32' testImplementation group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion } diff --git a/kafka-connect-rest-source/build.gradle b/kafka-connect-rest-source/build.gradle index 92282fab..58cbba9b 100644 --- a/kafka-connect-rest-source/build.gradle +++ b/kafka-connect-rest-source/build.gradle @@ -4,10 +4,10 @@ dependencies { // Included in connector runtime compileOnly group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion - testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.5' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.2' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.2' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.11.2' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.12.4' testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.27.2' testImplementation group: 'org.apache.kafka', name: 'connect-api', version: kafkaVersion From 28d004a2bd793432cdd2e66f0e07071e1d4f8e4b Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 16:47:48 +0200 Subject: [PATCH 10/23] Java updtes --- Dockerfile | 10 +++++----- build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 93cac6f5..e866f69a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM gradle:7.2-jdk8 as builder +FROM gradle:7.2-jdk11 as builder RUN mkdir /code WORKDIR /code @@ -22,19 +22,19 @@ ENV GRADLE_USER_HOME=/code/.gradlecache COPY ./build.gradle ./settings.gradle /code/ COPY kafka-connect-rest-source/build.gradle /code/kafka-connect-rest-source/ -RUN ./gradlew downloadDependencies copyDependencies +RUN gradle downloadDependencies copyDependencies COPY kafka-connect-fitbit-source/build.gradle /code/kafka-connect-fitbit-source/ -RUN ./gradlew downloadDependencies copyDependencies +RUN gradle downloadDependencies copyDependencies COPY ./kafka-connect-rest-source/src/ /code/kafka-connect-rest-source/src -RUN ./gradlew jar +RUN gradle jar COPY ./kafka-connect-fitbit-source/src/ /code/kafka-connect-fitbit-source/src -RUN ./gradlew jar +RUN gradle jar FROM confluentinc/cp-kafka-connect-base:6.2.0-3-ubi8 diff --git a/build.gradle b/build.gradle index 69c21d08..73cf7aae 100644 --- a/build.gradle +++ b/build.gradle @@ -27,8 +27,8 @@ subprojects { apply plugin: 'java' apply plugin: 'java-library' - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 11 + targetCompatibility = 11 } wrapper { From 1e65b86c56d4d409404ce6f56f1849cf59d5067c Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 17:02:37 +0200 Subject: [PATCH 11/23] Fixed launch script --- docker/launch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/launch b/docker/launch index a9ba474b..3aeb1ab7 100755 --- a/docker/launch +++ b/docker/launch @@ -46,7 +46,8 @@ echo "===> Launching ${COMPONENT} ..." # Add our jar to the classpath so that the custom classes can be loaded first. # And this also makes sure that the CLASSPATH does not start with ":/etc/..." # other jars are loaded via the plugin path -export CLASSPATH="/etc/${COMPONENT}/kafka-connect-mongodb-sink/*" +#export CLASSPATH="/etc/${COMPONENT}/kafka-connect-mongodb-sink/*" # execute connector in standalone mode -exec connect-standalone /etc/"${COMPONENT}"/"${COMPONENT}".properties $(find /etc/"${COMPONENT}"/ -type f -name "${CONNECTOR_PROPERTY_FILE_PREFIX}*.properties") +exec connect-standalone /etc/"${COMPONENT}"/"${COMPONENT}".properties /etc/"${COMPONENT}"/"${CONNECTOR_PROPERTY_FILE_PREFIX}"*.properties + From 16472588b5c2df4dfcedda4fb4dce907a8e4a960 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 6 Sep 2021 17:17:03 +0200 Subject: [PATCH 12/23] Fix launch config path --- docker/launch | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/launch b/docker/launch index 3aeb1ab7..6acc8510 100755 --- a/docker/launch +++ b/docker/launch @@ -50,4 +50,3 @@ echo "===> Launching ${COMPONENT} ..." # execute connector in standalone mode exec connect-standalone /etc/"${COMPONENT}"/"${COMPONENT}".properties /etc/"${COMPONENT}"/"${CONNECTOR_PROPERTY_FILE_PREFIX}"*.properties - From f5f70a3154babac4855f7bb52c5f66d7c3e82c1a Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 10:40:50 +0200 Subject: [PATCH 13/23] Add Github actions --- .github/workflows/main.yml | 139 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 96 +++++++++++++++++++++++ README.md | 4 +- 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..18a68628 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,139 @@ +# Continuous integration, including test and integration test +name: Main test + +# Run in master and dev branches and in all pull requests to those branches +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +env: + DOCKER_IMAGE: radarbase/kafka-connect-rest-fitbit-source + +jobs: + # Build and test the code + kotlin: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Cache + uses: actions/cache@v2 + with: + # Cache gradle directories + path: | + ~/.gradle/caches + ~/.gradle/wrapper + # Key for restoring and saving the cache + key: ${{ runner.os }}-gradle-${{ hashFiles('gradlew', '**/*.gradle', 'gradle.properties', 'gradle/**') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Compile the code + - name: Compile code + run: ./gradlew assemble + + # Gradle check + - name: Check + run: ./gradlew check + + docker: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Docker build parameters + id: docker_params + run: | + echo "::set-output name=has_docker_login::${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}" + if [ "${{ github.event_name == 'pull_request' }}" = "true" ]; then + echo "::set-output name=push::false" + echo "::set-output name=load::true" + echo "::set-output name=platforms::linux/amd64" + else + echo "::set-output name=push::true" + echo "::set-output name=load::false" + echo "::set-output name=platforms::linux/amd64,linux/arm64" + fi + + - name: Cache Docker layers + id: cache_buildx + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ steps.docker_params.outputs.push }}-${{ hashFiles('**/Dockerfile', '**/*.gradle', 'gradle.properties', '.dockerignore', '*/src/main/**', 'docker/**') }} + restore-keys: | + ${{ runner.os }}-buildx-${{ steps.docker_params.outputs.push }}- + ${{ runner.os }}-buildx- + + - name: Login to Docker Hub + if: steps.docker_params.outputs.has_docker_login == 'true' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Add Docker labels and tags + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v2 + with: + images: ${{ env.DOCKER_IMAGE }} + + # Setup docker build environment + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Cache parameters + id: cache-parameters + run: | + if [ "${{ steps.cache_buildx.outputs.cache-hit }}" = "true" ]; then + echo "::set-output name=cache-to::" + else + echo "::set-output name=cache-to::type=local,dest=/tmp/.buildx-cache-new,mode=max" + fi + + - name: Build docker + uses: docker/build-push-action@v2 + with: + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: ${{ steps.cache-parameters.outputs.cache-to }} + platforms: ${{ steps.docker_params.outputs.platforms }} + load: ${{ steps.docker_params.outputs.load }} + push: ${{ steps.docker_params.outputs.push }} + tags: ${{ steps.docker_meta.outputs.tags }} + # Use runtime labels from docker_meta as well as fixed labels + labels: | + ${{ steps.docker_meta.outputs.labels }} + maintainer=Joris Borgdorff , Nivethika Mahasivam , Pauline Conde + org.opencontainers.image.description=RADAR-base upload connector backend application + org.opencontainers.image.authors=Joris Borgdorff , Nivethika Mahasivam , Pauline Conde + org.opencontainers.image.vendor=RADAR-base + org.opencontainers.image.licenses=Apache-2.0 + + - name: Inspect docker images + run: | + docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + docker run --rm ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} curl --help + + - name: Move docker build cache + if: steps.cache_buildx.outputs.cache-hit != 'true' + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..af6b748e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,96 @@ +# Create release files +name: Release + +on: + release: + types: [ published ] + +env: + DOCKER_IMAGE: radarbase/kafka-connect-rest-fitbit-source + +jobs: + uploadBackend: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Gradle cache + uses: actions/cache@v2 + with: + # Cache gradle directories + path: | + ~/.gradle/caches + ~/.gradle/wrapper + # An explicit key for restoring and saving the cache + key: ${{ runner.os }}-gradle-${{ hashFiles('gradlew', '**/*.gradle', 'gradle.properties', 'gradle/**') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # Compile code + - name: Compile code + run: ./gradlew jar + + # Upload it to GitHub + - name: Upload to GitHub + uses: AButler/upload-release-assets@v2.0 + with: + files: "*/build/libs/*" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # Build and push tagged release docker image + docker: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v2 + + # Setup docker build environment + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Add Docker labels and tags + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v2 + with: + images: ${{ env.DOCKER_IMAGE }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build docker + uses: docker/build-push-action@v2 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.docker_meta.outputs.tags }} + # Use runtime labels from docker_meta_backend as well as fixed labels + labels: | + ${{ steps.docker_meta.outputs.labels }} + maintainer=Joris Borgdorff , Nivethika Mahasivam , Pauline Conde + org.opencontainers.image.description=RADAR-base upload connector backend application + org.opencontainers.image.authors=Joris Borgdorff , Nivethika Mahasivam , Pauline Conde + org.opencontainers.image.vendor=RADAR-base + org.opencontainers.image.licenses=Apache-2.0 + + - name: Inspect image + run: | + docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} diff --git a/README.md b/README.md index 82379590..7bac751a 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ Fitbit in particular. The documentation of the Kafka Connect REST source still n ### Installation This repository relies on a recent version of docker and docker-compose as well as an installation -of Java 8 or later. +of Java 11 or later. ### Usage +Generally, this component is installed with [RADAR-Kubernetes](https://github.com/RADAR-base/RADAR-Kubernetes). It uses Docker image [radarbase/kafka-connect-rest-fitbit-source](https://hub.docker.com/r/radarbase/kafka-connect-rest-fitbit-source). + First, [register a Fitbit App](https://dev.fitbit.com/apps) with Fitbit. It should be either a server app, for multiple users, or a personal app for a single user. With the server app, you need to [request access to intraday API data](https://dev.fitbit.com/build/reference/web-api/help/). From 245d3a7ebb9eff7832602d94b6f89afdf6e48e32 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 10:49:44 +0200 Subject: [PATCH 14/23] Remove redundant table separators --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7bac751a..b186e677 100644 --- a/README.md +++ b/README.md @@ -37,57 +37,57 @@ your Fitbit App client ID and client secret. The following tables shows the poss Importance -rest.source.poll.interval.msHow often to poll the source URL.long60000low +rest.source.poll.interval.msHow often to poll the source URL.long60000low -rest.source.base.urlBase URL for REST source connector.stringhigh +rest.source.base.urlBase URL for REST source connector.stringhigh -rest.source.destination.topicsThe list of destination topics for the REST source connector.list""high +rest.source.destination.topicsThe list of destination topics for the REST source connector.list""high -rest.source.topic.selectorThe topic selector class for REST source connector.classorg.radarbase.connect.rest.selector.SimpleTopicSelectorClass extending org.radarbase.connect.rest.selector.TopicSelectorhigh +rest.source.topic.selectorThe topic selector class for REST source connector.classorg.radarbase.connect.rest.selector.SimpleTopicSelectorClass extending org.radarbase.connect.rest.selector.TopicSelectorhigh -rest.source.payload.converter.classClass to be used to convert messages from REST calls to SourceRecordsclassorg.radarbase.connect.rest.converter.StringPayloadConverterClass extending org.radarbase.connect.rest.converter.PayloadToSourceRecordConverterlow +rest.source.payload.converter.classClass to be used to convert messages from REST calls to SourceRecordsclassorg.radarbase.connect.rest.converter.StringPayloadConverterClass extending org.radarbase.connect.rest.converter.PayloadToSourceRecordConverterlow -rest.source.request.generator.classClass to be used to generate REST requestsclassorg.radarbase.connect.rest.single.SingleRequestGeneratorClass extending org.radarbase.connect.rest.request.RequestGeneratorlow +rest.source.request.generator.classClass to be used to generate REST requestsclassorg.radarbase.connect.rest.single.SingleRequestGeneratorClass extending org.radarbase.connect.rest.request.RequestGeneratorlow -fitbit.usersThe user ID of Fitbit users to include in polling, separated by commas. Non existing user names will be ignored. If empty, all users in the user directory will be used.list""high +fitbit.usersThe user ID of Fitbit users to include in polling, separated by commas. Non existing user names will be ignored. If empty, all users in the user directory will be used.list""high -fitbit.api.clientClient ID for the Fitbit APIstringnon-empty stringhigh +fitbit.api.clientClient ID for the Fitbit APIstringnon-empty stringhigh -fitbit.api.secretSecret for the Fitbit API client set in fitbit.api.client.passwordhigh +fitbit.api.secretSecret for the Fitbit API client set in fitbit.api.client.passwordhigh -fitbit.user.poll.intervalPolling interval per Fitbit user per request route in seconds.int150medium +fitbit.user.poll.intervalPolling interval per Fitbit user per request route in seconds.int150medium -fitbit.api.intradaySet to true if the client has permissions to Fitbit Intraday API, false otherwise.booleanfalsemedium +fitbit.api.intradaySet to true if the client has permissions to Fitbit Intraday API, false otherwise.booleanfalsemedium -fitbit.user.repository.classClass for managing users and authentication.classorg.radarbase.connect.rest.fitbit.user.YamlUserRepositoryClass extending org.radarbase.connect.rest.fitbit.user.UserRepositorymedium +fitbit.user.repository.classClass for managing users and authentication.classorg.radarbase.connect.rest.fitbit.user.YamlUserRepositoryClass extending org.radarbase.connect.rest.fitbit.user.UserRepositorymedium -fitbit.user.dirDirectory containing Fitbit user information and credentials. Only used if a file-based user repository is configured.string/var/lib/kafka-connect-fitbit-source/userslow +fitbit.user.dirDirectory containing Fitbit user information and credentials. Only used if a file-based user repository is configured.string/var/lib/kafka-connect-fitbit-source/userslow -fitbit.user.repository.urlURL for webservice containing user credentials. Only used if a webservice-based user repository is configured.string""low +fitbit.user.repository.urlURL for webservice containing user credentials. Only used if a webservice-based user repository is configured.string""low -fitbit.user.repository.client.idClient ID for connecting to the service repository.string""medium +fitbit.user.repository.client.idClient ID for connecting to the service repository.string""medium -fitbit.user.repository.client.secretClient secret for connecting to the service repository.string""medium +fitbit.user.repository.client.secretClient secret for connecting to the service repository.string""medium -fitbit.user.repository.oauth2.token.urlOAuth 2.0 token url for retrieving client credentials.string""medium +fitbit.user.repository.oauth2.token.urlOAuth 2.0 token url for retrieving client credentials.string""medium -fitbit.intraday.steps.topicTopic for Fitbit intraday stepsstringconnect_fitbit_intraday_stepsnon-empty string without control characterslow +fitbit.intraday.steps.topicTopic for Fitbit intraday stepsstringconnect_fitbit_intraday_stepsnon-empty string without control characterslow -fitbit.intraday.heart.rate.topicTopic for Fitbit intraday heart_ratestringconnect_fitbit_intraday_heart_ratenon-empty string without control characterslow +fitbit.intraday.heart.rate.topicTopic for Fitbit intraday heart_ratestringconnect_fitbit_intraday_heart_ratenon-empty string without control characterslow -fitbit.sleep.stages.topicTopic for Fitbit sleep stagesstringconnect_fitbit_sleep_stagesnon-empty string without control characterslow +fitbit.sleep.stages.topicTopic for Fitbit sleep stagesstringconnect_fitbit_sleep_stagesnon-empty string without control characterslow -fitbit.sleep.classic.topicTopic for Fitbit sleep classic datastringconnect_fitbit_sleep_classicnon-empty string without control characterslow +fitbit.sleep.classic.topicTopic for Fitbit sleep classic datastringconnect_fitbit_sleep_classicnon-empty string without control characterslow -fitbit.time.zone.topicTopic for Fitbit profile time zonestringconnect_fitbit_time_zonenon-empty string without control characterslow +fitbit.time.zone.topicTopic for Fitbit profile time zonestringconnect_fitbit_time_zonenon-empty string without control characterslow -fitbit.activity.log.topicTopic for Fitbit activity log.stringconnect_fitbit_activity_lognon-empty string without control characterslow +fitbit.activity.log.topicTopic for Fitbit activity log.stringconnect_fitbit_activity_lognon-empty string without control characterslow -fitbit.intraday.calories.topicTopic for Fitbit intraday caloriesstringconnect_fitbit_intraday_caloriesnon-empty string without control characterslow +fitbit.intraday.calories.topicTopic for Fitbit intraday caloriesstringconnect_fitbit_intraday_caloriesnon-empty string without control characterslow -fitbit.user.firebase.collection.fitbit.nameFirestore Collection for retrieving Fitbit Auth details. Only used when a Firebase based user repository is used.stringfitbitlow +fitbit.user.firebase.collection.fitbit.nameFirestore Collection for retrieving Fitbit Auth details. Only used when a Firebase based user repository is used.stringfitbitlow -fitbit.user.firebase.collection.user.nameFirestore Collection for retrieving User details. Only used when a Firebase based user repository is used.stringuserslow +fitbit.user.firebase.collection.user.nameFirestore Collection for retrieving User details. Only used when a Firebase based user repository is used.stringuserslow If the ManagementPortal is used to authenticate against the user repository, please add an OAuth client to ManagementPortal with the following properties: From 62f44758c7ce20f90f1b978bd56f7985f73ce62f Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 10:57:44 +0200 Subject: [PATCH 15/23] Simplify docker image --- Dockerfile | 13 ++++--------- gradle.properties | 0 2 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 gradle.properties diff --git a/Dockerfile b/Dockerfile index e866f69a..15de33df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,21 +17,16 @@ FROM gradle:7.2-jdk11 as builder RUN mkdir /code WORKDIR /code -ENV GRADLE_USER_HOME=/code/.gradlecache +ENV GRADLE_USER_HOME=/code/.gradlecache \ + GRADLE_OPTS="-Dorg.gradle.vfs.watch=false" -COPY ./build.gradle ./settings.gradle /code/ +COPY ./build.gradle ./settings.gradle ./gradle.properties /code/ COPY kafka-connect-rest-source/build.gradle /code/kafka-connect-rest-source/ - -RUN gradle downloadDependencies copyDependencies - COPY kafka-connect-fitbit-source/build.gradle /code/kafka-connect-fitbit-source/ RUN gradle downloadDependencies copyDependencies COPY ./kafka-connect-rest-source/src/ /code/kafka-connect-rest-source/src - -RUN gradle jar - COPY ./kafka-connect-fitbit-source/src/ /code/kafka-connect-fitbit-source/src RUN gradle jar @@ -42,7 +37,7 @@ MAINTAINER Joris Borgdorff LABEL description="Kafka REST API Source connector" -ENV CONNECT_PLUGIN_PATH /usr/share/java/kafka-connect/plugins +ENV CONNECT_PLUGIN_PATH=/usr/share/java/kafka-connect/plugins # To isolate the classpath from the plugin path as recommended COPY --from=builder /code/kafka-connect-rest-source/build/third-party/*.jar ${CONNECT_PLUGIN_PATH}/kafka-connect-rest-source/ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..e69de29b From c683635e624cfc7a4481b4627e37e40ee23727f2 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 11:10:51 +0200 Subject: [PATCH 16/23] GA fix docker inspect --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 18a68628..32a225b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -129,6 +129,7 @@ jobs: - name: Inspect docker images run: | + docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker run --rm ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} curl --help From 39575de52a649b3ccb0341ce2f4061dc2582f642 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 13:54:50 +0200 Subject: [PATCH 17/23] Bump versions --- build.gradle | 2 +- docker-compose.yml | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 73cf7aae..e6ab1cd9 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ description = 'kafka-connect-rest-source' allprojects { group = "org.radarbase" - version = "3.2.0" + version = "3.3.0" repositories { mavenCentral() diff --git a/docker-compose.yml b/docker-compose.yml index cc5a01c4..dc5238a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: # Zookeeper Cluster # #---------------------------------------------------------------------------# zookeeper-1: - image: confluentinc/cp-zookeeper:5.5.1 + image: confluentinc/cp-zookeeper:6.2.0 environment: ZOOKEEPER_SERVER_ID: 1 ZOOKEEPER_CLIENT_PORT: 2181 @@ -19,7 +19,7 @@ services: ZOOKEEPER_SERVERS: zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888 zookeeper-2: - image: confluentinc/cp-zookeeper:5.5.1 + image: confluentinc/cp-zookeeper:6.2.0 environment: ZOOKEEPER_SERVER_ID: 2 ZOOKEEPER_CLIENT_PORT: 2181 @@ -29,7 +29,7 @@ services: ZOOKEEPER_SERVERS: zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888 zookeeper-3: - image: confluentinc/cp-zookeeper:5.5.1 + image: confluentinc/cp-zookeeper:6.2.0 environment: ZOOKEEPER_SERVER_ID: 3 ZOOKEEPER_CLIENT_PORT: 2181 @@ -42,7 +42,7 @@ services: # Kafka Cluster # #---------------------------------------------------------------------------# kafka-1: - image: confluentinc/cp-kafka:5.5.1 + image: confluentinc/cp-kafka:6.2.0 depends_on: - zookeeper-1 - zookeeper-2 @@ -61,7 +61,7 @@ services: KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false" kafka-2: - image: confluentinc/cp-kafka:5.5.1 + image: confluentinc/cp-kafka:6.2.0 depends_on: - zookeeper-1 - zookeeper-2 @@ -80,7 +80,7 @@ services: KAFKA_CONFLUENT_SUPPORT_METRICS_ENABLE: "false" kafka-3: - image: confluentinc/cp-kafka:5.5.1 + image: confluentinc/cp-kafka:6.2.0 depends_on: - zookeeper-1 - zookeeper-2 @@ -102,11 +102,8 @@ services: # Schema Registry # #---------------------------------------------------------------------------# schema-registry-1: - image: confluentinc/cp-schema-registry:5.5.1 + image: confluentinc/cp-schema-registry:6.2.0 depends_on: - - zookeeper-1 - - zookeeper-2 - - zookeeper-3 - kafka-1 - kafka-2 - kafka-3 @@ -114,7 +111,7 @@ services: ports: - "8081:8081" environment: - SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181 + SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka-1:9092,PLAINTEXT://kafka-2:9092,PLAINTEXT://kafka-3:9092 SCHEMA_REGISTRY_HOST_NAME: schema-registry-1 SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081 SCHEMA_REGISTRY_AVRO_COMPATIBILITY_LEVEL: none @@ -124,11 +121,8 @@ services: # REST proxy # #---------------------------------------------------------------------------# rest-proxy-1: - image: confluentinc/cp-kafka-rest:5.5.1 + image: confluentinc/cp-kafka-rest:6.2.0 depends_on: - - zookeeper-1 - - zookeeper-2 - - zookeeper-3 - kafka-1 - kafka-2 - kafka-3 @@ -136,6 +130,7 @@ services: ports: - "8082:8082" environment: + KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka-1:9092,PLAINTEXT://kafka-2:9092,PLAINTEXT://kafka-3:9092 KAFKA_REST_ZOOKEEPER_CONNECT: zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181 KAFKA_REST_LISTENERS: http://0.0.0.0:8082 KAFKA_REST_SCHEMA_REGISTRY_URL: http://schema-registry-1:8081 From 5eb0f59feaaf1e910f4298137560ce1b60add89d Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 13:56:29 +0200 Subject: [PATCH 18/23] Fix release number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e6ab1cd9..4322e22f 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ description = 'kafka-connect-rest-source' allprojects { group = "org.radarbase" - version = "3.3.0" + version = "0.4.0" repositories { mavenCentral() From 8c4444440539dd375f57f75e3c6ee0e67bca7738 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 14:11:52 +0200 Subject: [PATCH 19/23] Only pull image if it was pushed --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 32a225b1..2001dadf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -127,9 +127,12 @@ jobs: org.opencontainers.image.vendor=RADAR-base org.opencontainers.image.licenses=Apache-2.0 - - name: Inspect docker images + - name: Pull docker image + if: steps.docker_params.outputs.load == 'false' + run: docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} + + - name: Inspect docker image run: | - docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker image inspect ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} docker run --rm ${{ env.DOCKER_IMAGE }}:${{ steps.docker_meta.outputs.version }} curl --help From 017803a9e805cb8e1b80c54b87eddbe8b546d161 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 7 Sep 2021 15:08:15 +0200 Subject: [PATCH 20/23] Fix code issues --- .dockerignore | 2 -- .../converter/FitbitActivityLogAvroConverter.java | 2 +- .../rest/fitbit/request/FitbitRequestGenerator.java | 1 - .../rest/fitbit/route/FitbitIntradayStepsRoute.java | 1 - .../firebase/CovidCollabFirebaseUserRepository.java | 2 +- .../fitbit/user/firebase/FirebaseUserRepository.java | 7 +++---- .../rest/fitbit/user/firebase/FitbitTokenService.java | 6 +++--- .../radarbase/connect/rest/fitbit/util/DateRange.java | 6 ------ .../org/radarbase/connect/rest/RestSourceTask.java | 8 +------- .../connect/rest/converter/BytesPayloadConverter.java | 3 --- .../connect/rest/single/SingleRequestGenerator.java | 10 ++++------ .../rest/single/SingleRestSourceConnectorConfig.java | 1 - 12 files changed, 13 insertions(+), 36 deletions(-) diff --git a/.dockerignore b/.dockerignore index d3cc21fe..98eae927 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,8 +3,6 @@ .idea out build -*/out */src/test -*/build *.iml .gradletasknamecache diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitActivityLogAvroConverter.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitActivityLogAvroConverter.java index f441301c..254bb786 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitActivityLogAvroConverter.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitActivityLogAvroConverter.java @@ -156,7 +156,7 @@ private FitbitActivityHeartRate getHeartRate(JsonNode activity) { Optional mean = optInt(activity, "averageHeartRate"); Optional> zones = optArray(activity, "heartRateZones"); - if (!mean.isPresent() && !zones.isPresent()) { + if (mean.isEmpty() && zones.isEmpty()) { return null; } diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/request/FitbitRequestGenerator.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/request/FitbitRequestGenerator.java index b4b0a98f..8d062831 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/request/FitbitRequestGenerator.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/request/FitbitRequestGenerator.java @@ -24,7 +24,6 @@ import io.confluent.connect.avro.AvroData; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/route/FitbitIntradayStepsRoute.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/route/FitbitIntradayStepsRoute.java index 0ebc3d84..867e36fe 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/route/FitbitIntradayStepsRoute.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/route/FitbitIntradayStepsRoute.java @@ -20,7 +20,6 @@ import static java.time.temporal.ChronoUnit.MINUTES; import io.confluent.connect.avro.AvroData; -import java.time.temporal.TemporalAmount; import java.util.stream.Stream; import org.radarbase.connect.rest.fitbit.converter.FitbitIntradayStepsAvroConverter; import org.radarbase.connect.rest.fitbit.request.FitbitRequestGenerator; diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/CovidCollabFirebaseUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/CovidCollabFirebaseUserRepository.java index cb8ab697..89b9e8f7 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/CovidCollabFirebaseUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/CovidCollabFirebaseUserRepository.java @@ -122,7 +122,7 @@ public void initialize(RestSourceConnectorConfig config) { fitbitConfig.getFitbitClientSecret(), FITBIT_TOKEN_ENDPOINT); - /** + /* * Currently, we only listen for the fitbit collection, as it contains most information while * the user collection only contains project Id which is not supposed to change. The user * document is pulled every time the corresponding fitbit document is pulled, so it will be diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FirebaseUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FirebaseUserRepository.java index 198dccce..771ae9c9 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FirebaseUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FirebaseUserRepository.java @@ -34,10 +34,9 @@ public void initialize(RestSourceConnectorConfig config) { // See https://firebase.google.com/docs/admin/setup#initialize-sdk for more details. FirebaseOptions options; try { - options = - new FirebaseOptions.Builder() - .setCredentials(GoogleCredentials.getApplicationDefault()) - .build(); + options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.getApplicationDefault()) + .build(); } catch (IOException exc) { logger.error("Failed to get credentials for Firebase app.", exc); throw new IllegalStateException(exc); diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FitbitTokenService.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FitbitTokenService.java index 97f3311d..bfba3002 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FitbitTokenService.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/firebase/FitbitTokenService.java @@ -19,9 +19,9 @@ public class FitbitTokenService { private static final Logger logger = LoggerFactory.getLogger(FitbitTokenService.class); private final OkHttpClient client; private final ObjectMapper mapper; - private String clientId; - private String clientSecret; - private String tokenEndpoint; + private final String clientId; + private final String clientSecret; + private final String tokenEndpoint; public FitbitTokenService(String clientId, String clientSecret, String tokenEndpoint) { this.clientId = clientId; diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/util/DateRange.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/util/DateRange.java index b799f1bc..d5f61565 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/util/DateRange.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/util/DateRange.java @@ -18,7 +18,6 @@ package org.radarbase.connect.rest.fitbit.util; import java.time.ZonedDateTime; -import java.time.temporal.TemporalAmount; import java.util.Objects; public class DateRange { @@ -30,11 +29,6 @@ public DateRange(ZonedDateTime start, ZonedDateTime end) { this.end = end; } - public DateRange(ZonedDateTime start, TemporalAmount duration) { - this.start = start; - this.end = start.plus(duration); - } - public ZonedDateTime start() { return start; } diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java index b957496a..69077ab2 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java @@ -18,19 +18,13 @@ package org.radarbase.connect.rest; import static java.time.temporal.ChronoUnit.MILLIS; -import static org.radarbase.connect.rest.util.ThrowingFunction.tryOrNull; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.time.Instant; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.LongAdder; -import java.util.function.Function; import java.util.stream.Collectors; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.source.SourceRecord; @@ -42,7 +36,7 @@ import org.slf4j.LoggerFactory; public class RestSourceTask extends SourceTask { - private static Logger logger = LoggerFactory.getLogger(RestSourceTask.class); + private static final Logger logger = LoggerFactory.getLogger(RestSourceTask.class); private RequestGenerator requestGenerator; diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/converter/BytesPayloadConverter.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/converter/BytesPayloadConverter.java index 891ae478..69262954 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/converter/BytesPayloadConverter.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/converter/BytesPayloadConverter.java @@ -19,13 +19,10 @@ import static java.lang.System.currentTimeMillis; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Map; import okhttp3.Headers; -import okhttp3.Response; -import okhttp3.ResponseBody; import org.apache.kafka.connect.data.Schema; import org.apache.kafka.connect.source.SourceRecord; import org.radarbase.connect.rest.RestSourceConnectorConfig; diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRequestGenerator.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRequestGenerator.java index 4cff9842..a6298526 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRequestGenerator.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRequestGenerator.java @@ -17,6 +17,7 @@ package org.radarbase.connect.rest.single; +import static java.util.Objects.requireNonNullElse; import static org.radarbase.connect.rest.converter.PayloadToSourceRecordConverter.MIN_INSTANT; import static org.radarbase.connect.rest.converter.PayloadToSourceRecordConverter.TIMESTAMP_OFFSET_KEY; import static org.radarbase.connect.rest.request.PollingRequestRoute.max; @@ -25,6 +26,7 @@ import java.time.Instant; import java.util.Collections; import java.util.Map; +import java.util.Objects; import java.util.stream.Stream; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -75,12 +77,8 @@ public void initialize(RestSourceConnectorConfig config) { if (singleConfig.getData() != null && !singleConfig.getData().isEmpty()) { String contentType = headers.get("Content-Type"); - MediaType mediaType; - if (contentType == null) { - mediaType = MediaType.parse("text/plain; charset=utf-8"); - } else { - mediaType = MediaType.parse(contentType); - } + MediaType mediaType = MediaType.parse( + requireNonNullElse(contentType, "text/plain; charset=utf-8")); body = RequestBody.create(singleConfig.getData(), mediaType); } diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRestSourceConnectorConfig.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRestSourceConnectorConfig.java index e3c901d2..842a5dab 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRestSourceConnectorConfig.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/single/SingleRestSourceConnectorConfig.java @@ -47,7 +47,6 @@ public class SingleRestSourceConnectorConfig extends RestSourceConnectorConfig { private final Map requestProperties; - @SuppressWarnings("unchecked") private SingleRestSourceConnectorConfig(ConfigDef config, Map parsedConfig, boolean doLog) { super(config, parsedConfig, doLog); From dc7f9b051e4bf450c954b467298b7a9235d8d8c5 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 8 Sep 2021 14:54:13 +0200 Subject: [PATCH 21/23] Catch 407 protocol exception --- .../connect/rest/fitbit/user/ServiceUserRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java index 5e61ce36..a2761411 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/user/ServiceUserRepository.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; import java.io.IOException; +import java.net.ProtocolException; import java.net.URL; import java.time.Duration; import java.time.Instant; @@ -242,6 +243,8 @@ private T makeRequest(Request request, ObjectReader reader) throws IOExcepti logger.error("Failed to parse JSON: {}\n{}", ex, bodyString); throw ex; } + } catch (ProtocolException ex) { + throw new NotAuthorizedException("Refresh token cannot be retrieved for unauthorized user"); } } } From 99bfb07f168e434589f665b2b48ff56a630e0065 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 8 Sep 2021 15:04:08 +0200 Subject: [PATCH 22/23] Catch not authorized exception in appropriate places --- .../main/java/org/radarbase/connect/rest/RestSourceTask.java | 3 ++- .../java/org/radarbase/connect/rest/request/RestRequest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java index 69077ab2..a425f16a 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/RestSourceTask.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.ws.rs.NotAuthorizedException; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.source.SourceRecord; import org.apache.kafka.connect.source.SourceTask; @@ -86,7 +87,7 @@ public List poll() throws InterruptedException { try { requests = request.handleRequest() .collect(Collectors.toList()); - } catch (IOException ex) { + } catch (IOException | NotAuthorizedException ex) { logger.warn("Failed to make request: {}", ex.toString()); } } diff --git a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/request/RestRequest.java b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/request/RestRequest.java index 0abc06d2..8e7d5ce2 100644 --- a/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/request/RestRequest.java +++ b/kafka-connect-rest-source/src/main/java/org/radarbase/connect/rest/request/RestRequest.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.function.Predicate; import java.util.stream.Stream; +import javax.ws.rs.NotAuthorizedException; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -95,7 +96,7 @@ public Stream handleRequest() throws IOException { headers = response.headers(); ResponseBody body = response.body(); data = body != null ? body.bytes() : null; - } catch (IOException ex) { + } catch (IOException | NotAuthorizedException ex) { route.requestFailed(this, null); throw ex; } From 746d18d192cdbbd2dc04425734aa16a99171a432 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 8 Sep 2021 15:28:23 +0200 Subject: [PATCH 23/23] Fix #82 --- .../rest/fitbit/converter/FitbitSleepAvroConverter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitSleepAvroConverter.java b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitSleepAvroConverter.java index a6164a34..976a348b 100644 --- a/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitSleepAvroConverter.java +++ b/kafka-connect-fitbit-source/src/main/java/org/radarbase/connect/rest/fitbit/converter/FitbitSleepAvroConverter.java @@ -127,6 +127,10 @@ protected Stream processRecords( }) .collect(Collectors.toList()); + if (allRecords.isEmpty()) { + return Stream.empty(); + } + // The final group gets the actual offset, to ensure that the group does not get queried // again. allRecords.get(allRecords.size() - 1).sourceOffset = startTime;