From 73467cb4abd8cf15efe1d10035688bda7260cbdb Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 23 Sep 2019 11:32:48 +0200 Subject: [PATCH 01/13] Bumped dev version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d99e2841..68715844 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ subprojects { // Configuration // //---------------------------------------------------------------------------// - version = '0.12.2' + version = '0.12.3-SNAPSHOT' group = 'org.radarbase' ext.githubRepoName = 'RADAR-base/radar-commons' From fe2de41b40c1e7c75eecfa3ef4220055170970ce Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 23 Sep 2019 11:33:24 +0200 Subject: [PATCH 02/13] Updated dependencies --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 68715844..2e70e5bd 100644 --- a/build.gradle +++ b/build.gradle @@ -36,8 +36,8 @@ subprojects { ext.slf4jVersion = '1.7.26' ext.kafkaVersion = '2.3.0' ext.avroVersion = '1.8.2' - ext.confluentVersion = '5.3.0' - ext.jacksonVersion = '2.9.9.1' + ext.confluentVersion = '5.3.1' + ext.jacksonVersion = '2.9.9.2' ext.jacksonYamlVersion = '2.9.9' ext.okhttpVersion = '4.0.1' ext.junitVersion = '4.12' From 1e1c71213690cafdd25929aae4ab55c47eb0f38c Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 23 Dec 2019 16:55:36 +0100 Subject: [PATCH 03/13] More flexible SchemaRetriever and RestClient --- .../radarbase/producer/rest/RestClient.java | 11 +++++- .../producer/rest/SchemaRetriever.java | 39 ++++++------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/radar-commons/src/main/java/org/radarbase/producer/rest/RestClient.java b/radar-commons/src/main/java/org/radarbase/producer/rest/RestClient.java index 24cd6d4b..9bc29727 100644 --- a/radar-commons/src/main/java/org/radarbase/producer/rest/RestClient.java +++ b/radar-commons/src/main/java/org/radarbase/producer/rest/RestClient.java @@ -25,6 +25,7 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.Callback; +import okhttp3.Headers; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.OkHttpClient; @@ -46,10 +47,12 @@ public class RestClient { private final ServerConfig server; private final OkHttpClient httpClient; + private final Headers headers; private RestClient(Builder builder) { this.server = Objects.requireNonNull(builder.serverConfig); this.httpClient = builder.client.build(); + this.headers = builder.requestHeaders; } /** OkHttp client. */ @@ -132,7 +135,7 @@ public String requestString(Request request) throws IOException { * @throws MalformedURLException if the path not valid */ public Request.Builder requestBuilder(String relativePath) throws MalformedURLException { - return new Request.Builder().url(getRelativeUrl(relativePath)); + return new Request.Builder().url(getRelativeUrl(relativePath)).headers(headers); } /** @@ -201,6 +204,7 @@ public Builder newBuilder() { public static class Builder { private ServerConfig serverConfig; private final OkHttpClient.Builder client; + private Headers requestHeaders = Headers.of(); public Builder(OkHttpClient client) { this(client.newBuilder()); @@ -239,6 +243,11 @@ public OkHttpClient.Builder httpClientBuilder() { return client; } + public Builder headers(Headers headers) { + this.requestHeaders = headers; + return this; + } + /** Whether to enable GZIP compression. */ public Builder gzipCompression(boolean compression) { GzipRequestInterceptor gzip = null; diff --git a/radar-commons/src/main/java/org/radarbase/producer/rest/SchemaRetriever.java b/radar-commons/src/main/java/org/radarbase/producer/rest/SchemaRetriever.java index 7c9f83e4..ef5e5ce7 100644 --- a/radar-commons/src/main/java/org/radarbase/producer/rest/SchemaRetriever.java +++ b/radar-commons/src/main/java/org/radarbase/producer/rest/SchemaRetriever.java @@ -60,8 +60,12 @@ public class SchemaRetriever { PRIMITIVE_SCHEMAS.put(byte[].class, Schema.create(Type.BYTES)); } - private final ConcurrentMap cache; - private RestClient httpClient; + private final ConcurrentMap cache = new ConcurrentHashMap<>(); + private final RestClient restClient; + + private SchemaRetriever(RestClient client) { + restClient = client; + } /** * Schema retriever for a Confluent Schema Registry. @@ -69,27 +73,10 @@ public class SchemaRetriever { * @param connectionTimeout timeout in seconds. */ public SchemaRetriever(ServerConfig config, long connectionTimeout) { - cache = new ConcurrentHashMap<>(); - httpClient = RestClient.global() - .server(Objects.requireNonNull(config)) - .timeout(connectionTimeout, TimeUnit.SECONDS) - .build(); - } - - /** - * Set the connection timeout. - * @param connectionTimeout timeout in seconds. - */ - public synchronized void setConnectionTimeout(long connectionTimeout) { - if (httpClient.getTimeout() != connectionTimeout) { - httpClient = httpClient.newBuilder() - .timeout(connectionTimeout, TimeUnit.SECONDS) - .build(); - } - } - - private synchronized RestClient getRestClient() { - return httpClient; + this(RestClient.global() + .server(Objects.requireNonNull(config)) + .timeout(connectionTimeout, TimeUnit.SECONDS) + .build()); } /** The subject in the Avro Schema Registry, given a Kafka topic. */ @@ -109,7 +96,6 @@ protected ParsedSchemaMetadata retrieveSchemaMetadata(String subject, int versio } else { pathBuilder.append("latest"); } - RestClient restClient = getRestClient(); Request request = restClient.requestBuilder(pathBuilder.toString()) .addHeader("Accept", "application/json") .build(); @@ -147,7 +133,6 @@ public void addSchemaMetadata(String topic, boolean ofValue, ParsedSchemaMetadat throws JSONException, IOException { String subject = subject(topic, ofValue); if (metadata.getId() == null) { - RestClient restClient = getRestClient(); Request request = restClient.requestBuilder("/subjects/" + subject + "/versions") .addHeader("Accept", "application/json") @@ -179,7 +164,7 @@ public ParsedSchemaMetadata getOrSetSchemaMetadata(String topic, boolean ofValue } } - private class SchemaRequestBody extends RequestBody { + private static class SchemaRequestBody extends RequestBody { private final Schema schema; private SchemaRequestBody(Schema schema) { @@ -221,7 +206,7 @@ public static Schema getSchema(Object object) { + "Pass null, a primitive CONTENT_TYPE or a GenericContainer."); } - private class TimedSchemaMetadata { + private static class TimedSchemaMetadata { private final ParsedSchemaMetadata metadata; private final long expiry; From c153af0a6f3a6039b1e5b8ee76a453656142ad95 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 23 Dec 2019 16:55:59 +0100 Subject: [PATCH 04/13] Updated dependencies --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 2e70e5bd..c5979240 100644 --- a/build.gradle +++ b/build.gradle @@ -33,18 +33,18 @@ subprojects { group = 'org.radarbase' ext.githubRepoName = 'RADAR-base/radar-commons' - ext.slf4jVersion = '1.7.26' + ext.slf4jVersion = '1.7.27' ext.kafkaVersion = '2.3.0' ext.avroVersion = '1.8.2' ext.confluentVersion = '5.3.1' - ext.jacksonVersion = '2.9.9.2' - ext.jacksonYamlVersion = '2.9.9' - ext.okhttpVersion = '4.0.1' + ext.jacksonVersion = '2.10.1' + ext.jacksonYamlVersion = '2.10.1' + ext.okhttpVersion = '4.2.2' ext.junitVersion = '4.12' ext.mockitoVersion = '2.28.2' ext.hamcrestVersion = '1.3' ext.codacyVersion = '6.0.0' - ext.radarSchemasVersion = '0.5.1' + ext.radarSchemasVersion = '0.5.5' ext.orgJsonVersion = '20180813' ext.githubUrl = "https://github.com/$githubRepoName" From e0aeeb211bb095610ef4dda39dbf2b93fcbe28d2 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Tue, 24 Dec 2019 15:32:06 +0100 Subject: [PATCH 05/13] Update gradle --- README.md | 10 +++---- build.gradle | 8 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 33 ++++++++++------------- radar-commons-server/build.gradle | 2 +- radar-commons/build.gradle | 2 +- 7 files changed, 26 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 06ceebe6..a29c9889 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ For server utilities, include `radar-commons-server`: ```gradle repositories { jcenter() - maven { url 'http://packages.confluent.io/maven/' } + maven { url 'https://packages.confluent.io/maven/' } } dependencies { @@ -78,8 +78,8 @@ For mocking clients of the RADAR-base infrastructure, use that 'radar-commons-te ```gradle repositories { jcenter() - maven { url 'http://packages.confluent.io/maven/' } - maven { url 'http://dl.bintray.com/radar-base/org.radarbase' } + maven { url 'https://packages.confluent.io/maven/' } + maven { url 'https://dl.bintray.com/radar-base/org.radarbase' } } dependencies { @@ -91,8 +91,8 @@ Finally, if the schema registry is losing old schemas and your code is not recov ```gradle repositories { jcenter() - maven { url 'http://packages.confluent.io/maven/' } - maven { url 'http://dl.bintray.com/radar-base/org.radarbase' } + maven { url 'https://packages.confluent.io/maven/' } + maven { url 'https://dl.bintray.com/radar-base/org.radarbase' } } dependencies { diff --git a/build.gradle b/build.gradle index c5979240..d5f82b7f 100644 --- a/build.gradle +++ b/build.gradle @@ -49,15 +49,15 @@ subprojects { ext.githubUrl = "https://github.com/$githubRepoName" ext.issueUrl = "https://github.com/$githubRepoName/issues" - ext.website = 'http://radar-base.org' + ext.website = 'https://radar-base.org' //---------------------------------------------------------------------------// // Dependencies // //---------------------------------------------------------------------------// repositories { jcenter() - maven { url 'http://packages.confluent.io/maven/' } - maven { url 'http://dl.bintray.com/typesafe/maven-releases' } + maven { url 'https://packages.confluent.io/maven/' } + maven { url 'https://dl.bintray.com/typesafe/maven-releases' } flatDir { dirs "${project.rootDir}/libs" } @@ -111,5 +111,5 @@ subprojects { } wrapper { - gradleVersion '5.5.1' + gradleVersion '6.0.1' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644 GIT binary patch delta 16535 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 Date: Wed, 22 Apr 2020 11:07:17 +0200 Subject: [PATCH 06/13] Dependency updates --- build.gradle | 20 +++++++++--------- gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 3 +++ radar-commons/build.gradle | 4 ++-- .../producer/rest/AvroDataMapperFactory.java | 6 +++--- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index d5f82b7f..00f47a98 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ */ plugins { // Get bintray version - id 'com.jfrog.bintray' version '1.8.4' apply false + id 'com.jfrog.bintray' version '1.8.5' apply false id 'com.commercehub.gradle.plugin.avro' version '0.16.0' } @@ -33,19 +33,19 @@ subprojects { group = 'org.radarbase' ext.githubRepoName = 'RADAR-base/radar-commons' - ext.slf4jVersion = '1.7.27' + ext.slf4jVersion = '1.7.30' ext.kafkaVersion = '2.3.0' ext.avroVersion = '1.8.2' ext.confluentVersion = '5.3.1' - ext.jacksonVersion = '2.10.1' - ext.jacksonYamlVersion = '2.10.1' - ext.okhttpVersion = '4.2.2' + ext.jacksonVersion = '2.10.3' + ext.jacksonYamlVersion = '2.10.3' + ext.okhttpVersion = '4.5.0' ext.junitVersion = '4.12' - ext.mockitoVersion = '2.28.2' + ext.mockitoVersion = '3.3.3' ext.hamcrestVersion = '1.3' - ext.codacyVersion = '6.0.0' - ext.radarSchemasVersion = '0.5.5' - ext.orgJsonVersion = '20180813' + ext.codacyVersion = '7.9.0' + ext.radarSchemasVersion = '0.5.7' + ext.orgJsonVersion = '20190722' ext.githubUrl = "https://github.com/$githubRepoName" ext.issueUrl = "https://github.com/$githubRepoName/issues" @@ -111,5 +111,5 @@ subprojects { } wrapper { - gradleVersion '6.0.1' + gradleVersion '6.3' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 18348 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 H|2F2obH5>< delta 18428 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94920145..a4b44297 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.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 24467a14..9109989e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" diff --git a/radar-commons/build.gradle b/radar-commons/build.gradle index 5bfa4cdd..57880421 100644 --- a/radar-commons/build.gradle +++ b/radar-commons/build.gradle @@ -1,7 +1,7 @@ description = 'RADAR Common utilities library.' -targetCompatibility = '1.7' -sourceCompatibility = '1.7' +targetCompatibility = '1.8' +sourceCompatibility = '1.8' //---------------------------------------------------------------------------// // Sources and classpath configurations // diff --git a/radar-commons/src/main/java/org/radarbase/producer/rest/AvroDataMapperFactory.java b/radar-commons/src/main/java/org/radarbase/producer/rest/AvroDataMapperFactory.java index 5f8e0a09..e7a84623 100644 --- a/radar-commons/src/main/java/org/radarbase/producer/rest/AvroDataMapperFactory.java +++ b/radar-commons/src/main/java/org/radarbase/producer/rest/AvroDataMapperFactory.java @@ -382,10 +382,10 @@ private AvroDataMapper mapMap(Schema from, Schema to) throws SchemaValidationExc @Override public Object convertAvro(Object obj) { @SuppressWarnings("unchecked") - Map map = (Map) obj; + Map map = (Map) obj; Map toMap = new HashMap<>(map.size() * 4 / 3 + 1); - for (Map.Entry entry : map.entrySet()) { - toMap.put(entry.getKey(), subMapper.convertAvro(entry.getValue())); + for (Map.Entry entry : map.entrySet()) { + toMap.put(entry.getKey().toString(), subMapper.convertAvro(entry.getValue())); } return toMap; } From b693dcbc75ecc29c4316d04ce912b081f05e475a Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 22 Apr 2020 11:26:32 +0200 Subject: [PATCH 07/13] Updated testing dependencies --- radar-commons-testing/build.gradle | 2 +- .../main/java/org/radarbase/mock/MockFileSender.java | 3 +++ .../main/java/org/radarbase/mock/MockProducer.java | 12 ++++++++++-- .../java/org/radarbase/mock/data/MockCsvParser.java | 6 ++++-- .../org/radarbase/mock/data/MockRecordValidator.java | 3 ++- .../org/radarbase/mock/model/MockAggregator.java | 5 ++++- .../java/org/radarbase/mock/CsvGeneratorTest.java | 7 ++++--- 7 files changed, 28 insertions(+), 10 deletions(-) diff --git a/radar-commons-testing/build.gradle b/radar-commons-testing/build.gradle index bbe4da33..9e4c1734 100644 --- a/radar-commons-testing/build.gradle +++ b/radar-commons-testing/build.gradle @@ -46,7 +46,7 @@ dependencies { api group: 'org.apache.avro', name: 'avro', version: avroVersion api group: 'org.radarcns', name: 'radar-schemas-commons', version: radarSchemasVersion - implementation group: 'com.opencsv', name: 'opencsv', version: '4.6' + implementation group: 'com.opencsv', name: 'opencsv', version: '5.1' implementation group: 'com.fasterxml.jackson.core' , name: 'jackson-databind' , version: jacksonVersion implementation group: 'org.apache.kafka', name: 'kafka-clients', version: kafkaVersion implementation (group: 'io.confluent', name: 'kafka-avro-serializer', version: confluentVersion) { diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/MockFileSender.java b/radar-commons-testing/src/main/java/org/radarbase/mock/MockFileSender.java index f1638213..34bb5ade 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/MockFileSender.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/MockFileSender.java @@ -16,6 +16,7 @@ package org.radarbase.mock; +import com.opencsv.exceptions.CsvValidationException; import java.io.IOException; import org.apache.avro.SchemaValidationException; import org.radarbase.data.Record; @@ -50,6 +51,8 @@ public void send() throws IOException { } } catch (SchemaValidationException e) { throw new IOException("Failed to match schemas", e); + } catch (CsvValidationException e) { + throw new IOException("Failed to read CSV file", e); } } } diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java b/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java index d70772cf..adf9f77f 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java @@ -21,6 +21,7 @@ import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; +import com.opencsv.exceptions.CsvValidationException; import io.confluent.kafka.serializers.KafkaAvroSerializer; import java.io.IOException; import java.nio.file.Path; @@ -59,7 +60,7 @@ * A Mock Producer class that can be used to stream data. It can use MockFileSender and MockDevice * for testing purposes, with direct or indirect streaming. */ -@SuppressWarnings("PMD.DoNotCallSystemExit") +@SuppressWarnings("PMD") public class MockProducer { private static final Logger logger = LoggerFactory.getLogger(MockProducer.class); @@ -120,6 +121,13 @@ public MockProducer(BasicMockConfig mockConfig, Path root) throws IOException { for (int i = 0; i < mockFiles.size(); i++) { files.add(new MockFileSender(tmpSenders.get(i + numDevices), mockFiles.get(i))); } + } catch (CsvValidationException ex) { + if (tmpSenders != null) { + for (KafkaSender sender : tmpSenders) { + sender.close(); + } + } + throw new IOException("Cannot read CSV file", ex); } catch (Exception ex) { if (tmpSenders != null) { for (KafkaSender sender : tmpSenders) { @@ -345,7 +353,7 @@ private List> createGenerators(List> createMockFiles(List configs, - Path dataRoot) throws IOException { + Path dataRoot) throws IOException, CsvValidationException { List> result = new ArrayList<>(configs.size()); diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockCsvParser.java b/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockCsvParser.java index 0c3e4dcd..9efc4184 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockCsvParser.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockCsvParser.java @@ -17,6 +17,7 @@ package org.radarbase.mock.data; import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvValidationException; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; @@ -55,7 +56,8 @@ public class MockCsvParser implements Closeable { * @param root parent directory of the data file. * @throws IllegalArgumentException if the second row has the wrong number of columns */ - public MockCsvParser(MockDataConfig config, Path root) throws IOException { + public MockCsvParser(MockDataConfig config, Path root) + throws IOException, CsvValidationException { //noinspection unchecked topic = config.parseAvroTopic(); @@ -80,7 +82,7 @@ public AvroTopic getTopic() { * @throws IllegalStateException if a next row is not available * @throws IOException if the next row could not be read */ - public Record next() throws IOException { + public Record next() throws IOException, CsvValidationException { if (!hasNext()) { throw new IllegalStateException("No next record available"); } diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockRecordValidator.java b/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockRecordValidator.java index 607fc459..f36ad179 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockRecordValidator.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/data/MockRecordValidator.java @@ -16,6 +16,7 @@ package org.radarbase.mock.data; +import com.opencsv.exceptions.CsvValidationException; import java.io.IOException; import java.nio.file.Path; import org.apache.avro.specific.SpecificRecord; @@ -71,7 +72,7 @@ public void validate() { checkDuration(); checkFrequency(line); - } catch (IOException e) { + } catch (IOException | CsvValidationException e) { error("Cannot open file", -1, e); } } diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/model/MockAggregator.java b/radar-commons-testing/src/main/java/org/radarbase/mock/model/MockAggregator.java index 099ada0d..e649a73e 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/model/MockAggregator.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/model/MockAggregator.java @@ -16,6 +16,7 @@ package org.radarbase.mock.model; +import com.opencsv.exceptions.CsvValidationException; import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; @@ -81,9 +82,11 @@ public Map simulate() throws IOException { } expectedValue.put(config, value); + } catch (CsvValidationException ex) { + throw new IOException(ex); } } return expectedValue; } -} \ No newline at end of file +} diff --git a/radar-commons-testing/src/test/java/org/radarbase/mock/CsvGeneratorTest.java b/radar-commons-testing/src/test/java/org/radarbase/mock/CsvGeneratorTest.java index e713976c..b6cd45a9 100644 --- a/radar-commons-testing/src/test/java/org/radarbase/mock/CsvGeneratorTest.java +++ b/radar-commons-testing/src/test/java/org/radarbase/mock/CsvGeneratorTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals; import com.opencsv.CSVReader; +import com.opencsv.exceptions.CsvValidationException; import java.io.IOException; import java.io.Reader; import java.nio.file.Files; @@ -45,7 +46,7 @@ private MockDataConfig makeConfig() throws IOException { } @Test - public void generateMockConfig() throws IOException { + public void generateMockConfig() throws IOException, CsvValidationException { CsvGenerator generator = new CsvGenerator(); MockDataConfig config = makeConfig(); @@ -73,7 +74,7 @@ public void generateMockConfig() throws IOException { } @Test - public void generateGenerator() throws IOException { + public void generateGenerator() throws IOException, CsvValidationException { CsvGenerator generator = new CsvGenerator(); MockDataConfig config = makeConfig(); @@ -107,4 +108,4 @@ public Iterable iteratableRawValues(ObservationKey key, long duration) parser.readNext()); } } -} \ No newline at end of file +} From 7d65de8abddad4d1af97401e6600195989d275c8 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 22 Apr 2020 14:36:32 +0200 Subject: [PATCH 08/13] Updated versions and Kafka version --- README.md | 10 +++++----- build.gradle | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a29c9889..6521172b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ repositories { } dependencies { - implementation group: 'org.radarbase', name: 'radar-commons', version: '0.12.1' + implementation group: 'org.radarbase', name: 'radar-commons', version: '0.12.3' } ``` @@ -69,7 +69,7 @@ repositories { } dependencies { - implementation group: 'org.radarbase', name: 'radar-commons-server', version: '0.12.1' + implementation group: 'org.radarbase', name: 'radar-commons-server', version: '0.12.3' } ``` @@ -83,7 +83,7 @@ repositories { } dependencies { - testImplementation group: 'org.radarbase', name: 'radar-commons-testing', version: '0.12.1' + testImplementation group: 'org.radarbase', name: 'radar-commons-testing', version: '0.12.3' } ``` @@ -96,7 +96,7 @@ repositories { } dependencies { - runtimeOnly group: 'org.radarbase', name: 'radar-commons-unsafe', version: '0.12.1' + runtimeOnly group: 'org.radarbase', name: 'radar-commons-unsafe', version: '0.12.3' } ``` @@ -121,7 +121,7 @@ configurations.all { } dependencies { - compile group: 'org.radarbase', name: 'radar-commons', version: '0.12.2-SNAPSHOT' + compile group: 'org.radarbase', name: 'radar-commons', version: '0.12.4-SNAPSHOT' } ``` diff --git a/build.gradle b/build.gradle index 00f47a98..2068384c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,14 +29,14 @@ subprojects { // Configuration // //---------------------------------------------------------------------------// - version = '0.12.3-SNAPSHOT' + version = '0.12.3' group = 'org.radarbase' ext.githubRepoName = 'RADAR-base/radar-commons' ext.slf4jVersion = '1.7.30' - ext.kafkaVersion = '2.3.0' + ext.kafkaVersion = '2.4.0' ext.avroVersion = '1.8.2' - ext.confluentVersion = '5.3.1' + ext.confluentVersion = '5.4.1' ext.jacksonVersion = '2.10.3' ext.jacksonYamlVersion = '2.10.3' ext.okhttpVersion = '4.5.0' From 335f675341c254c1f6838d83324de36fbcb59d52 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 10 Jun 2020 15:01:10 +0200 Subject: [PATCH 09/13] Bumped dependencies --- build.gradle | 20 +++++++++---------- .../producer/rest/RestTopicSender.java | 5 +---- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 2068384c..05c480b0 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { // Get bintray version id 'com.jfrog.bintray' version '1.8.5' apply false - id 'com.commercehub.gradle.plugin.avro' version '0.16.0' + id 'com.commercehub.gradle.plugin.avro' version '0.19.1' } subprojects { @@ -34,18 +34,18 @@ subprojects { ext.githubRepoName = 'RADAR-base/radar-commons' ext.slf4jVersion = '1.7.30' - ext.kafkaVersion = '2.4.0' - ext.avroVersion = '1.8.2' - ext.confluentVersion = '5.4.1' - ext.jacksonVersion = '2.10.3' - ext.jacksonYamlVersion = '2.10.3' - ext.okhttpVersion = '4.5.0' - ext.junitVersion = '4.12' + ext.kafkaVersion = '2.5.0' + ext.avroVersion = '1.9.2' + ext.confluentVersion = '5.5.0' + ext.jacksonVersion = '2.11.0' + ext.jacksonYamlVersion = '2.11.0' + ext.okhttpVersion = '4.7.2' + ext.junitVersion = '4.13' ext.mockitoVersion = '3.3.3' ext.hamcrestVersion = '1.3' ext.codacyVersion = '7.9.0' - ext.radarSchemasVersion = '0.5.7' - ext.orgJsonVersion = '20190722' + ext.radarSchemasVersion = '0.5.9' + ext.orgJsonVersion = '20200518' ext.githubUrl = "https://github.com/$githubRepoName" ext.issueUrl = "https://github.com/$githubRepoName/issues" diff --git a/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java b/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java index 93656210..cc7f0923 100644 --- a/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java +++ b/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java @@ -86,10 +86,7 @@ public void send(RecordData records) throws IOException, SchemaValidationE try (Response response = context.client.request(request)) { if (response.isSuccessful()) { state.didConnect(); - if (logger.isDebugEnabled()) { - logger.debug("Added message to topic {} -> {}", - topic, responseBody(response)); - } + logger.debug("Added message to topic {}", topic); } else if (response.code() == 401 || response.code() == 403) { state.wasUnauthorized(); } else if (response.code() == 415) { From 992bf060dc05cd8148125c68984985e2d1c50fc0 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 10 Jun 2020 15:02:11 +0200 Subject: [PATCH 10/13] Bump opencsv version --- radar-commons-testing/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radar-commons-testing/build.gradle b/radar-commons-testing/build.gradle index 9e4c1734..b2d4cc94 100644 --- a/radar-commons-testing/build.gradle +++ b/radar-commons-testing/build.gradle @@ -46,7 +46,7 @@ dependencies { api group: 'org.apache.avro', name: 'avro', version: avroVersion api group: 'org.radarcns', name: 'radar-schemas-commons', version: radarSchemasVersion - implementation group: 'com.opencsv', name: 'opencsv', version: '5.1' + implementation group: 'com.opencsv', name: 'opencsv', version: '5.2' implementation group: 'com.fasterxml.jackson.core' , name: 'jackson-databind' , version: jacksonVersion implementation group: 'org.apache.kafka', name: 'kafka-clients', version: kafkaVersion implementation (group: 'io.confluent', name: 'kafka-avro-serializer', version: confluentVersion) { From 4b93b03f5a54c8c281f0fc106eab31cb7019a302 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 10 Jun 2020 15:04:24 +0200 Subject: [PATCH 11/13] Removed reference to Jackson 1.x --- .../src/main/java/org/radarbase/data/StringEncoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radar-commons/src/main/java/org/radarbase/data/StringEncoder.java b/radar-commons/src/main/java/org/radarbase/data/StringEncoder.java index 0ebc4cd2..0169be8d 100644 --- a/radar-commons/src/main/java/org/radarbase/data/StringEncoder.java +++ b/radar-commons/src/main/java/org/radarbase/data/StringEncoder.java @@ -16,12 +16,12 @@ package org.radarbase.data; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; import java.io.IOException; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; import org.apache.avro.SchemaValidationException; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.map.ObjectWriter; import org.radarbase.producer.rest.ParsedSchemaMetadata; /** Encodes a String as Avro. */ From 4f8c7a04bafb3936f547107784d2ec57fe0bdfef Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 10 Jun 2020 16:24:05 +0200 Subject: [PATCH 12/13] Update deserializer to latest version --- radar-commons-unsafe/build.gradle | 2 +- .../AbstractKafkaAvroDeserializer.java | 423 ++++++++++-------- 2 files changed, 244 insertions(+), 181 deletions(-) diff --git a/radar-commons-unsafe/build.gradle b/radar-commons-unsafe/build.gradle index ceacc03d..1d148256 100644 --- a/radar-commons-unsafe/build.gradle +++ b/radar-commons-unsafe/build.gradle @@ -21,7 +21,7 @@ repositories { dependencies { compileOnly group: 'io.confluent', name: 'kafka-avro-serializer', version: confluentVersion compileOnly group: 'org.apache.kafka', name: 'kafka-clients', version: kafkaVersion - compileOnly group: 'org.apache.kafka', name: 'kafka_2.11', version: kafkaVersion + compileOnly group: 'org.apache.kafka', name: 'kafka_2.12', version: kafkaVersion } diff --git a/radar-commons-unsafe/src/main/java/io/confluent/kafka/serializers/AbstractKafkaAvroDeserializer.java b/radar-commons-unsafe/src/main/java/io/confluent/kafka/serializers/AbstractKafkaAvroDeserializer.java index a7f1222c..6f847a60 100644 --- a/radar-commons-unsafe/src/main/java/io/confluent/kafka/serializers/AbstractKafkaAvroDeserializer.java +++ b/radar-commons-unsafe/src/main/java/io/confluent/kafka/serializers/AbstractKafkaAvroDeserializer.java @@ -16,45 +16,44 @@ package io.confluent.kafka.serializers; +import io.confluent.kafka.schemaregistry.avro.AvroSchema; +import io.confluent.kafka.schemaregistry.avro.AvroSchemaProvider; +import io.confluent.kafka.schemaregistry.avro.AvroSchemaUtils; +import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import kafka.utils.VerifiableProperties; import org.apache.avro.Schema; import org.apache.avro.generic.GenericContainer; import org.apache.avro.generic.GenericDatumReader; import org.apache.avro.io.DatumReader; import org.apache.avro.io.DecoderFactory; +import org.apache.avro.reflect.ReflectData; +import org.apache.avro.reflect.ReflectDatumReader; import org.apache.avro.specific.SpecificData; import org.apache.avro.specific.SpecificDatumReader; import org.apache.avro.specific.SpecificRecord; import org.apache.kafka.common.errors.SerializationException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; -import io.confluent.kafka.schemaregistry.client.SchemaMetadata; -import kafka.utils.VerifiableProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class AbstractKafkaAvroDeserializer extends AbstractKafkaAvroSerDe { +public abstract class AbstractKafkaAvroDeserializer extends AbstractKafkaSchemaSerDe { private final ConcurrentMap> oldToNewIdMap = new ConcurrentHashMap<>(); - private final ConcurrentMap> oldToNewVersionMap = new ConcurrentHashMap<>(); - - private static final Logger logger = LoggerFactory.getLogger(AbstractKafkaAvroDeserializer.class); private final DecoderFactory decoderFactory = DecoderFactory.get(); protected boolean useSpecificAvroReader = false; - private final Map readerSchemaCache = new ConcurrentHashMap(); + private final Map readerSchemaCache = new ConcurrentHashMap<>(); /** * Sets properties for this deserializer without overriding the schema registry client itself. * Useful for testing, where a mock client is injected. */ protected void configure(KafkaAvroDeserializerConfig config) { - configureClientProperties(config); + configureClientProperties(config, new AvroSchemaProvider()); useSpecificAvroReader = config .getBoolean(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG); } @@ -67,14 +66,6 @@ protected KafkaAvroDeserializerConfig deserializerConfig(VerifiableProperties pr return new KafkaAvroDeserializerConfig(props.props()); } - private ByteBuffer getByteBuffer(byte[] payload) { - ByteBuffer buffer = ByteBuffer.wrap(payload); - if (buffer.get() != MAGIC_BYTE) { - throw new SerializationException("Unknown magic byte!"); - } - return buffer; - } - /** * Deserializes the payload without including schema information for primitive types, maps, and * arrays. Just the resulting deserialized object is returned. @@ -85,7 +76,7 @@ private ByteBuffer getByteBuffer(byte[] payload) { * @return the deserialized object */ protected Object deserialize(byte[] payload) throws SerializationException { - return deserialize(false, null, null, payload, null); + return deserialize(null, null, payload, null); } /** @@ -96,128 +87,29 @@ protected Object deserialize(byte[] payload) throws SerializationException { * @return the deserialized object */ protected Object deserialize(byte[] payload, Schema readerSchema) throws SerializationException { - return deserialize(false, null, null, payload, readerSchema); + return deserialize(null, null, payload, readerSchema); } - // The Object return type is a bit messy, but this is the simplest way to have - // flexible decoding and not duplicate deserialization code multiple times for different variants. - protected Object deserialize(boolean includeSchemaAndVersion, String topic, Boolean isKey, - byte[] payload, Schema readerSchema) throws SerializationException { - // Even if the caller requests schema & version, if the payload is null we cannot include it. - // The caller must handle this case. + protected Object deserialize(String topic, Boolean isKey, byte[] payload, Schema readerSchema) + throws SerializationException { if (payload == null) { return null; } - int id = -1; - try { - ByteBuffer buffer = getByteBuffer(payload); - id = buffer.getInt(); - Schema schema; - String subject = null; - if (includeSchemaAndVersion) { - subject = subjectName(topic, isKey, null); - } - SchemaMetadata schemaMetadata = null; - ConcurrentMap subjectIdMap = oldToNewIdMap.computeIfAbsent(subject, sub -> new ConcurrentHashMap<>()); - try { - id = subjectIdMap.getOrDefault(id, id); - schema = schemaForDeserialize(id, null, subject, isKey); - } catch (RestClientException ex) { - if (ex.getErrorCode() == 40403) { - logger.debug("Trying to get id from Latest SchemaMetadata from schemaRegistry for subject {}", subject); - int oldId = id; - schemaMetadata = schemaRegistry.getLatestSchemaMetadata(subject); - id = schemaMetadata.getId(); - schema = new Schema.Parser().parse(schemaMetadata.getSchema()); - // keep a track of a subject's ids map so that subsequent records don't query the wrong schema id - subjectIdMap.put(oldId, id); - logger.debug("success -> schemaMetadata.getId({}) for subject {}", id, subject); - } else { - throw ex; - } - } - - int length = buffer.limit() - 1 - idSize; - final Object result; - if (schema.getType().equals(Schema.Type.BYTES)) { - byte[] bytes = new byte[length]; - buffer.get(bytes, 0, length); - result = bytes; - } else { - int start = buffer.position() + buffer.arrayOffset(); - DatumReader reader = getDatumReader(schema, readerSchema); - Object - object = - reader.read(null, decoderFactory.binaryDecoder(buffer.array(), start, length, null)); - - if (schema.getType().equals(Schema.Type.STRING)) { - object = object.toString(); // Utf8 -> String - } - result = object; - } - - if (includeSchemaAndVersion) { - // Annotate the schema with the version. Note that we only do this if the schema + - // version are requested, i.e. in Kafka Connect converters. This is critical because that - // code *will not* rely on exact schema equality. Regular deserializers *must not* include - // this information because it would return schemas which are not equivalent. - // - // Note, however, that we also do not fill in the connect.version field. This allows the - // Converter to let a version provided by a Kafka Connect source take priority over the - // schema registry's ordering (which is implicit by auto-registration time rather than - // explicit from the Connector). - Integer version; - if (schemaMetadata != null) { - version = schemaMetadata.getVersion(); - } else { - Map schemaVersionMap = oldToNewVersionMap.computeIfAbsent(subject, sub -> new ConcurrentHashMap<>()); - version = schemaVersionMap.get(schema); - - if (version == null) { - try { - version = schemaRegistry.getVersion(subject, schema); - } catch (RestClientException e) { - if (e.getErrorCode() == 40403) { - logger.debug("Trying to get version from Latest SchemaMetadata from schemaRegistry for subject {}", subject); - schemaMetadata = schemaRegistry.getLatestSchemaMetadata(subject); - version = schemaMetadata.getVersion(); - - schemaVersionMap.putIfAbsent(schema, version); - logger.debug("success -> schemaMetadata.getVersion({}) for subject {}", version, subject); - } else { - throw e; - } - } - } - } - - if (schema.getType().equals(Schema.Type.RECORD)) { - return new GenericContainerWithVersion((GenericContainer) result, version); - } else { - return new GenericContainerWithVersion(new NonRecordContainer(schema, result), version); - } - } else { - return result; - } - } catch (IOException | RuntimeException e) { - // avro deserialization may throw AvroRuntimeException, NullPointerException, etc - throw new SerializationException("Error deserializing Avro message for id " + id, e); - } catch (RestClientException e) { - throw new SerializationException("Error retrieving Avro schema for id " + id, e); - } + DeserializationContext context = new DeserializationContext(topic, isKey, payload); + return context.read(context.schemaFromRegistry().rawSchema(), readerSchema); } private Integer schemaVersion(String topic, Boolean isKey, int id, String subject, - Schema schema, + AvroSchema schema, Object result) throws IOException, RestClientException { Integer version; if (isDeprecatedSubjectNameStrategy(isKey)) { subject = getSubjectName(topic, isKey, result, schema); - Schema subjectSchema = schemaRegistry.getBySubjectAndId(subject, id); + AvroSchema subjectSchema = (AvroSchema) schemaRegistry.getSchemaBySubjectAndId(subject, id); version = schemaRegistry.getVersion(subject, subjectSchema); } else { //we already got the subject name @@ -226,21 +118,12 @@ private Integer schemaVersion(String topic, return version; } - private String subjectName(String topic, Boolean isKey, Schema schemaFromRegistry) { + private String subjectName(String topic, Boolean isKey, AvroSchema schemaFromRegistry) { return isDeprecatedSubjectNameStrategy(isKey) ? null : getSubjectName(topic, isKey, null, schemaFromRegistry); } - private Schema schemaForDeserialize(int id, - Schema schemaFromRegistry, - String subject, - Boolean isKey) throws IOException, RestClientException { - return isDeprecatedSubjectNameStrategy(isKey) - ? AvroSchemaUtils.copyOf(schemaFromRegistry) - : schemaRegistry.getBySubjectAndId(subject, id); - } - /** * Deserializes the payload and includes schema information, with version information from the * schema registry embedded in the schema. @@ -249,56 +132,236 @@ private Schema schemaForDeserialize(int id, * @return a GenericContainer with the schema and data, either as a {@link NonRecordContainer}, * {@link org.apache.avro.generic.GenericRecord}, or {@link SpecificRecord} */ - protected GenericContainerWithVersion deserializeWithSchemaAndVersion(String topic, boolean isKey, - byte[] payload) + protected GenericContainerWithVersion deserializeWithSchemaAndVersion( + String topic, boolean isKey, byte[] payload) throws SerializationException { - return (GenericContainerWithVersion) deserialize(true, topic, isKey, payload, null); + // Even if the caller requests schema & version, if the payload is null we cannot include it. + // The caller must handle this case. + if (payload == null) { + return null; + } + + // Annotate the schema with the version. Note that we only do this if the schema + + // version are requested, i.e. in Kafka Connect converters. This is critical because that + // code *will not* rely on exact schema equality. Regular deserializers *must not* include + // this information because it would return schemas which are not equivalent. + // + // Note, however, that we also do not fill in the connect.version field. This allows the + // Converter to let a version provided by a Kafka Connect source take priority over the + // schema registry's ordering (which is implicit by auto-registration time rather than + // explicit from the Connector). + DeserializationContext context = new DeserializationContext(topic, isKey, payload); + AvroSchema schema = context.schemaForDeserialize(); + Object result = context.read(schema.rawSchema(), null); + + try { + Integer version = schemaVersion(topic, isKey, context.getSchemaId(), + context.getSubject(), schema, result); + if (schema.rawSchema().getType().equals(Schema.Type.RECORD)) { + return new GenericContainerWithVersion((GenericContainer) result, version); + } else { + return new GenericContainerWithVersion(new NonRecordContainer(schema.rawSchema(), result), + version); + } + } catch (RestClientException | IOException e) { + throw new SerializationException("Error retrieving Avro " + + getSchemaType(isKey) + + " schema version for id " + + context.getSchemaId(), e); + } } - private DatumReader getDatumReader(Schema writerSchema, Schema readerSchema) { + protected DatumReader getDatumReader(Schema writerSchema, Schema readerSchema) { + // normalize reader schema + readerSchema = getReaderSchema(writerSchema, readerSchema); + boolean writerSchemaIsPrimitive = + AvroSchemaUtils.getPrimitiveSchemas().containsValue(writerSchema); + if (writerSchemaIsPrimitive) { + return new GenericDatumReader<>(writerSchema, readerSchema); + } else if (useSchemaReflection) { + return new ReflectDatumReader<>(writerSchema, readerSchema); + } else if (useSpecificAvroReader) { + return new SpecificDatumReader<>(writerSchema, readerSchema); + } else { + return new GenericDatumReader<>(writerSchema, readerSchema); + } + } + + /** + * Normalizes the reader schema, puts the resolved schema into the cache. + *
  • + *
      if the reader schema is provided, use the provided one
    + *
      if the reader schema is cached for the writer schema full name, use the cached value
    + *
      if the writer schema is primitive, use the writer one
    + *
      if schema reflection is used, generate one from the class referred by writer schema
    + *
      if generated classes are used, query the class referred by writer schema
    + *
      otherwise use the writer schema
    + *
  • + */ + private Schema getReaderSchema(Schema writerSchema, Schema readerSchema) { + if (readerSchema != null) { + return readerSchema; + } + readerSchema = readerSchemaCache.get(writerSchema.getFullName()); + if (readerSchema != null) { + return readerSchema; + } boolean writerSchemaIsPrimitive = AvroSchemaUtils.getPrimitiveSchemas().values().contains(writerSchema); - // do not use SpecificDatumReader if writerSchema is a primitive - if (useSpecificAvroReader && !writerSchemaIsPrimitive) { - if (readerSchema == null) { - readerSchema = getReaderSchema(writerSchema); - } - return new SpecificDatumReader(writerSchema, readerSchema); + if (writerSchemaIsPrimitive) { + readerSchema = writerSchema; + } else if (useSchemaReflection) { + readerSchema = getReflectionReaderSchema(writerSchema); + readerSchemaCache.put(writerSchema.getFullName(), readerSchema); + } else if (useSpecificAvroReader) { + readerSchema = getSpecificReaderSchema(writerSchema); + readerSchemaCache.put(writerSchema.getFullName(), readerSchema); } else { - if (readerSchema == null) { - return new GenericDatumReader(writerSchema); - } - return new GenericDatumReader(writerSchema, readerSchema); + readerSchema = writerSchema; } + return readerSchema; } @SuppressWarnings("unchecked") - private Schema getReaderSchema(Schema writerSchema) { - Schema readerSchema = readerSchemaCache.get(writerSchema.getFullName()); - if (readerSchema == null) { - Class readerClass = SpecificData.get().getClass(writerSchema); - if (readerClass != null) { - try { - readerSchema = readerClass.newInstance().getSchema(); - } catch (InstantiationException e) { - throw new SerializationException(writerSchema.getFullName() - + " specified by the " - + "writers schema could not be instantiated to " - + "find the readers schema."); - } catch (IllegalAccessException e) { - throw new SerializationException(writerSchema.getFullName() - + " specified by the " - + "writers schema is not allowed to be instantiated " - + "to find the readers schema."); + private Schema getSpecificReaderSchema(Schema writerSchema) { + Class readerClass = SpecificData.get().getClass(writerSchema); + if (readerClass == null) { + throw new SerializationException("Could not find class " + + writerSchema.getFullName() + + " specified in writer's schema whilst finding reader's " + + "schema for a SpecificRecord."); + } + try { + return readerClass.getConstructor().newInstance().getSchema(); + } catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) { + throw new SerializationException(writerSchema.getFullName() + + " specified by the " + + "writers schema could not be instantiated to " + + "find the readers schema."); + } catch (IllegalAccessException e) { + throw new SerializationException(writerSchema.getFullName() + + " specified by the " + + "writers schema is not allowed to be instantiated " + + "to find the readers schema."); + } + } + + private Schema getReflectionReaderSchema(Schema writerSchema) { + // shall we use ReflectData.AllowNull.get() instead? + Class readerClass = ReflectData.get().getClass(writerSchema); + if (readerClass == null) { + throw new SerializationException("Could not find class " + + writerSchema.getFullName() + + " specified in writer's schema whilst finding reader's " + + "schema for a reflected class."); + } + return ReflectData.get().getSchema(readerClass); + } + + class DeserializationContext { + private final String topic; + private final Boolean isKey; + private final ByteBuffer buffer; + private final int schemaId; + + DeserializationContext(final String topic, final Boolean isKey, final byte[] payload) { + this.topic = topic; + this.isKey = isKey; + this.buffer = getByteBuffer(payload); + this.schemaId = buffer.getInt(); + } + + AvroSchema schemaFromRegistry() { + String subject = getSubject(); + ConcurrentMap subjectIdMap = oldToNewIdMap.computeIfAbsent(subject, sub -> new ConcurrentHashMap<>()); + int id = subjectIdMap.getOrDefault(schemaId, schemaId); + try { + return (AvroSchema) schemaRegistry.getSchemaBySubjectAndId(subject, id); + } catch (RestClientException e) { + if (e.getErrorCode() == 40403) { + try { + List versions = schemaRegistry.getAllVersions(subject); + int latestId = versions.get(versions.size() - 1); + subjectIdMap.put(schemaId, latestId); + + return (AvroSchema) schemaRegistry.getSchemaBySubjectAndId(subject, latestId); + } catch (RestClientException | IOException ex) { + throw new SerializationException("Error retrieving Avro " + + getSchemaType(isKey) + + " schema for id " + + schemaId, e); + } } - readerSchemaCache.put(writerSchema.getFullName(), readerSchema); + throw new SerializationException("Error retrieving Avro " + + getSchemaType(isKey) + + " schema for id " + + schemaId, e); + } catch (IOException e) { + throw new SerializationException("Error retrieving Avro " + + getSchemaType(isKey) + + " schema for id " + + schemaId, e); + } + } + + AvroSchema schemaForDeserialize() { + return schemaFromRegistry(); + } + + String getSubject() { + return subjectName(topic, isKey, schemaFromRegistry()); + } + + String getTopic() { + return topic; + } + + boolean isKey() { + return isKey; + } + + + int getSchemaId() { + return schemaId; + } + + Object read(Schema writerSchema) { + return read(writerSchema, null); + } + + Object read(Schema writerSchema, Schema readerSchema) { + DatumReader reader = getDatumReader(writerSchema, readerSchema); + int length = buffer.limit() - 1 - idSize; + if (writerSchema.getType().equals(Schema.Type.BYTES)) { + byte[] bytes = new byte[length]; + buffer.get(bytes, 0, length); + return bytes; } else { - throw new SerializationException("Could not find class " - + writerSchema.getFullName() - + " specified in writer's schema whilst finding reader's " - + "schema for a SpecificRecord."); + int start = buffer.position() + buffer.arrayOffset(); + try { + Object result = reader.read(null, decoderFactory.binaryDecoder(buffer.array(), + start, length, null)); + if (writerSchema.getType().equals(Schema.Type.STRING)) { + return result.toString(); + } else { + return result; + } + } catch (IOException | RuntimeException e) { + // avro deserialization may throw AvroRuntimeException, NullPointerException, etc + throw new SerializationException("Error deserializing Avro message for id " + + schemaId, e); + } } } - return readerSchema; + } + + private static String getSchemaType(Boolean isKey) { + if (isKey == null) { + return "unknown"; + } else if (isKey) { + return "key"; + } else { + return "value"; + } } } From c3328e74d8d17b4bb4d28656a1e944a02ad90044 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Wed, 10 Jun 2020 16:25:20 +0200 Subject: [PATCH 13/13] Removed deprecated calls --- .../java/org/radarbase/mock/MockProducer.java | 10 ++--- .../mock/data/MockRecordValidatorTest.java | 39 ++++++++++--------- .../producer/rest/RestTopicSender.java | 1 - .../producer/rest/SchemaRetrieverTest.java | 27 +++++-------- 4 files changed, 35 insertions(+), 42 deletions(-) diff --git a/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java b/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java index adf9f77f..36d1fc0f 100644 --- a/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java +++ b/radar-commons-testing/src/main/java/org/radarbase/mock/MockProducer.java @@ -16,7 +16,7 @@ package org.radarbase.mock; -import static io.confluent.kafka.serializers.AbstractKafkaAvroSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG; +import static io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP_SERVERS_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; @@ -193,7 +193,7 @@ private List createRestSenders(int numDevices, /** Start sending data. */ public void start() throws IOException { - for (MockDevice device : devices) { + for (MockDevice device : devices) { device.start(); } for (MockFileSender file : files) { @@ -205,11 +205,11 @@ public void start() throws IOException { public void shutdown() throws IOException, InterruptedException, SchemaValidationException { if (!devices.isEmpty()) { logger.info("Shutting down mock devices"); - for (MockDevice device : devices) { + for (MockDevice device : devices) { device.shutdown(); } logger.info("Waiting for mock devices to finish..."); - for (MockDevice device : devices) { + for (MockDevice device : devices) { device.join(5_000L); } } @@ -218,7 +218,7 @@ public void shutdown() throws IOException, InterruptedException, SchemaValidatio sender.close(); } - for (MockDevice device : devices) { + for (MockDevice device : devices) { device.checkException(); } } diff --git a/radar-commons-testing/src/test/java/org/radarbase/mock/data/MockRecordValidatorTest.java b/radar-commons-testing/src/test/java/org/radarbase/mock/data/MockRecordValidatorTest.java index 1204cf64..43308b78 100644 --- a/radar-commons-testing/src/test/java/org/radarbase/mock/data/MockRecordValidatorTest.java +++ b/radar-commons-testing/src/test/java/org/radarbase/mock/data/MockRecordValidatorTest.java @@ -16,6 +16,8 @@ package org.radarbase.mock.data; +import static org.junit.Assert.assertThrows; + import java.io.IOException; import java.io.Writer; import java.nio.file.Files; @@ -34,8 +36,6 @@ public class MockRecordValidatorTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); - @Rule - public ExpectedException exception = ExpectedException.none(); private Path root; private MockDataConfig makeConfig() throws IOException { @@ -63,7 +63,6 @@ public void validate() throws Exception { MockDataConfig config = makeConfig(); generator.generate(config, 100_000L, root); - // doesn't throw new MockRecordValidator(config, 100_000L, root).validate(); } @@ -74,8 +73,7 @@ public void validateWrongDuration() throws Exception { MockDataConfig config = makeConfig(); generator.generate(config, 100_000L, root); - exception.expect(IllegalArgumentException.class); - new MockRecordValidator(config, 10_000L, root).validate(); + assertValidateThrows(IllegalArgumentException.class, config); } @Test @@ -88,7 +86,7 @@ public void validateCustom() throws Exception { writer.append("test,a,b,1,2,1\n"); } - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidate(config); } @Test @@ -101,8 +99,7 @@ public void validateWrongKey() throws Exception { writer.append("test,a,c,1,2,1\n"); } - exception.expect(IllegalArgumentException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(IllegalArgumentException.class, config); } @Test @@ -115,8 +112,7 @@ public void validateWrongTime() throws Exception { writer.append("test,a,b,1,0,1\n"); } - exception.expect(IllegalArgumentException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(IllegalArgumentException.class, config); } @@ -130,8 +126,7 @@ public void validateMissingKeyField() throws Exception { writer.append("test,a,1,2,1\n"); } - exception.expect(NullPointerException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(NullPointerException.class, config); } @Test @@ -144,8 +139,7 @@ public void validateMissingValueField() throws Exception { writer.append("test,a,b,1,2\n"); } - exception.expect(NullPointerException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(NullPointerException.class, config); } @Test @@ -158,8 +152,7 @@ public void validateMissingValue() throws Exception { writer.append("test,a,b,1,2,1\n"); } - exception.expect(ArrayIndexOutOfBoundsException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(ArrayIndexOutOfBoundsException.class, config); } @Test @@ -172,8 +165,7 @@ public void validateWrongValueType() throws Exception { writer.append("test,a,b,1,2,b\n"); } - exception.expect(NumberFormatException.class); - new MockRecordValidator(config, 2_000L, root).validate(); + assertValidateThrows(NumberFormatException.class, config); } @Test @@ -188,6 +180,15 @@ public void validateMultipleFields() throws Exception { writer.append("test,a,b,1,2,1,1,1\n"); } + assertValidate(config); + } + + private void assertValidateThrows(Class ex, MockDataConfig config) { + MockRecordValidator validator = new MockRecordValidator(config, 2_000L, root); + assertThrows(ex, validator::validate); + } + + private void assertValidate(MockDataConfig config) { new MockRecordValidator(config, 2_000L, root).validate(); } -} \ No newline at end of file +} diff --git a/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java b/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java index cc7f0923..147ce268 100644 --- a/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java +++ b/radar-commons/src/main/java/org/radarbase/producer/rest/RestTopicSender.java @@ -16,7 +16,6 @@ package org.radarbase.producer.rest; -import static org.radarbase.producer.rest.RestClient.responseBody; import static org.radarbase.producer.rest.UncheckedRequestException.fail; import java.io.IOException; diff --git a/radar-commons/src/test/java/org/radarbase/producer/rest/SchemaRetrieverTest.java b/radar-commons/src/test/java/org/radarbase/producer/rest/SchemaRetrieverTest.java index 38d283ab..a56abae0 100644 --- a/radar-commons/src/test/java/org/radarbase/producer/rest/SchemaRetrieverTest.java +++ b/radar-commons/src/test/java/org/radarbase/producer/rest/SchemaRetrieverTest.java @@ -17,6 +17,7 @@ package org.radarbase.producer.rest; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import java.io.IOException; import java.util.Collections; @@ -28,23 +29,17 @@ import org.apache.avro.Schema.Field; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.radarbase.config.ServerConfig; public class SchemaRetrieverTest { private MockWebServer server; - private ServerConfig config; private SchemaRetriever retriever; - @Rule - public ExpectedException exception = ExpectedException.none(); - @Before public void setUp() { server = new MockWebServer(); - config = new ServerConfig(); + ServerConfig config = new ServerConfig(); config.setProtocol("http"); config.setHost(server.getHostName()); config.setPort(server.getPort()); @@ -58,7 +53,7 @@ public void tearDown() throws IOException { } @Test - public void subject() throws Exception { + public void subject() { assertEquals("bla-value", SchemaRetriever.subject("bla", true)); assertEquals("bla-key", SchemaRetriever.subject("bla", false)); } @@ -95,16 +90,15 @@ public void getSchemaMetadata() throws Exception { // Already queried schema is cached and does not need another request ParsedSchemaMetadata metadata2 = retriever.getSchemaMetadata("bla", true, -1); - assertEquals(Integer.valueOf(10), metadata.getId()); - assertEquals(Integer.valueOf(2), metadata.getVersion()); - assertEquals(Schema.create(Schema.Type.STRING), metadata.getSchema()); + assertEquals(Integer.valueOf(10), metadata2.getId()); + assertEquals(Integer.valueOf(2), metadata2.getVersion()); + assertEquals(Schema.create(Schema.Type.STRING), metadata2.getSchema()); assertEquals(1, server.getRequestCount()); // Not yet queried schema needs a new request, so if the server does not respond, an // IOException is thrown. server.enqueue(new MockResponse().setResponseCode(500)); - exception.expect(IOException.class); - retriever.getSchemaMetadata("bla", false, 2); + assertThrows(IOException.class, () -> retriever.getSchemaMetadata("bla", false, 2)); } @Test @@ -122,9 +116,8 @@ public void addSchemaMetadata() throws Exception { List schemaFields = Collections.singletonList( new Field("a", Schema.create(Schema.Type.INT), "that a", 10)); - Schema record = Schema.createRecord("C", "that C", "org.radarcns", false); - metadata = new ParsedSchemaMetadata(null, null, - Schema.createRecord("C", "that C", "org.radarcns", false, schemaFields)); + Schema record = Schema.createRecord("C", "that C", "org.radarcns", false, schemaFields); + metadata = new ParsedSchemaMetadata(null, null, record); server.enqueue(new MockResponse().setBody("{\"id\":11}")); retriever.addSchemaMetadata("bla", true, metadata); assertEquals(Integer.valueOf(11), metadata.getId()); @@ -167,4 +160,4 @@ public void getOrSetSchemaMetadataGet() throws Exception { assertEquals(Integer.valueOf(10), metadata.getId()); assertEquals(Schema.create(Schema.Type.STRING), metadata.getSchema()); } -} \ No newline at end of file +}