From 355c087bc9db5990d8dc5cbd8a87ad14f1468a56 Mon Sep 17 00:00:00 2001 From: jruaux Date: Thu, 25 Apr 2024 02:03:42 -0700 Subject: [PATCH] refactor!: Moved redis args --- VERSION | 2 +- .../com/redis/riot/faker/FakerItemReader.java | 12 + .../com/redis/riot/file/FileDumpExport.java | 5 - .../redis/riot/file/AbstractFileTests.java | 23 +- .../com/redis/riot/redis/Replication.java | 24 +- .../redis/riot/redis/ReplicationTests.java | 9 +- core/riot-core/riot-core.gradle | 23 +- .../com/redis/riot/core/AbstractExport.java | 100 +----- .../redis/riot/core/AbstractMapExport.java | 5 - .../riot/core/AbstractRedisCallable.java | 28 +- .../riot/core/ExportProcessorOptions.java | 58 ---- .../com/redis/riot/core/KeyFilterOptions.java | 40 +++ .../riot/core/KeyValueProcessorOptions.java | 108 ++++++ .../redis/riot/core/RedisClientOptions.java | 326 +++--------------- .../redis/riot/core/RedisReaderOptions.java | 40 ++- .../java/com/redis/riot/core/RiotUtils.java | 37 ++ .../java/com/redis/riot/core/RiotVersion.java | 59 ++++ .../redis/riot/core/RiotVersion.properties | 4 + .../com/redis/riot/core/ProcessorTests.java | 26 +- gradle.properties | 2 +- .../redis/riot/cli/AbstractExportCommand.java | 72 ++-- .../redis/riot/cli/AbstractImportCommand.java | 100 ++---- .../redis/riot/cli/AbstractMainCommand.java | 43 ++- .../riot/cli/AbstractMapImportCommand.java | 86 +++++ .../redis/riot/cli/AbstractRiotCommand.java | 142 ++++---- .../riot/cli/AbstractStructImportCommand.java | 21 -- .../java/com/redis/riot/cli/BaseCommand.java | 31 -- .../com/redis/riot/cli/DbExportCommand.java | 16 +- .../com/redis/riot/cli/DbImportCommand.java | 18 +- .../redis/riot/cli/FakerImportCommand.java | 23 +- .../redis/riot/cli/FileDumpImportCommand.java | 14 +- ...ortCommand.java => FileExportCommand.java} | 22 +- .../com/redis/riot/cli/FileImportCommand.java | 38 +- .../com/redis/riot/cli/GenerateCommand.java | 76 ++-- .../main/java/com/redis/riot/cli/JobArgs.java | 121 +++++++ .../redis/riot/cli/KeyValueProcessorArgs.java | 36 +- .../{LoggingMixin.java => LoggingArgs.java} | 6 +- .../main/java/com/redis/riot/cli/Main.java | 2 +- .../riot/cli/ManifestVersionProvider.java | 27 -- .../java/com/redis/riot/cli/PingCommand.java | 85 ++++- .../cli/ProgressStepExecutionListener.java | 61 ++-- .../java/com/redis/riot/cli/RedisArgs.java | 304 ---------------- .../com/redis/riot/cli/RedisClientArgs.java | 93 +++++ ...ommand.java => RedisOperationCommand.java} | 2 +- .../com/redis/riot/cli/RedisReaderArgs.java | 20 +- .../java/com/redis/riot/cli/RedisURIArgs.java | 178 ++++++++++ .../com/redis/riot/cli/ReplicateCommand.java | 125 ++++--- .../redis/riot/cli/ReplicateSourceArgs.java | 43 +++ .../redis/riot/cli/ReplicateTargetArgs.java | 107 ++++++ .../main/java/com/redis/riot/cli/SslArgs.java | 117 +++++++ .../java/com/redis/riot/cli/Versions.java | 18 + ...> AbstractCollectionOperationCommand.java} | 2 +- ...ava => AbstractRedisOperationCommand.java} | 4 +- .../com/redis/riot/cli/redis/DelCommand.java | 2 +- .../redis/riot/cli/redis/ExpireCommand.java | 2 +- .../redis/riot/cli/redis/GeoaddCommand.java | 2 +- .../com/redis/riot/cli/redis/HsetCommand.java | 2 +- .../redis/riot/cli/redis/JsonSetCommand.java | 2 +- .../redis/riot/cli/redis/LpushCommand.java | 2 +- .../redis/riot/cli/redis/RpushCommand.java | 2 +- .../com/redis/riot/cli/redis/SaddCommand.java | 2 +- .../com/redis/riot/cli/redis/SetCommand.java | 2 +- .../redis/riot/cli/redis/SugaddCommand.java | 2 +- .../redis/riot/cli/redis/TsAddCommand.java | 2 +- .../com/redis/riot/cli/redis/XaddCommand.java | 2 +- .../com/redis/riot/cli/redis/ZaddCommand.java | 2 +- .../com/redis/riot/cli/Banner.properties | 4 + .../resources/com/redis/riot/cli/banner.txt | 6 + .../redis/riot/cli/AbstractRiotTestBase.java | 31 +- .../cli/StackToStackIntegrationTests.java | 2 +- plugins/riot/src/test/resources/replicate | 2 +- .../riot/src/test/resources/replicate-dry-run | 2 +- plugins/riot/src/test/resources/replicate-hll | 2 +- .../src/test/resources/replicate-key-exclude | 2 +- .../test/resources/replicate-key-processor | 2 +- .../riot/src/test/resources/replicate-live | 2 +- .../test/resources/replicate-live-key-exclude | 2 +- .../src/test/resources/replicate-live-keyslot | 2 +- .../test/resources/replicate-live-only-struct | 2 +- .../src/test/resources/replicate-live-struct | 2 +- .../src/test/resources/replicate-live-threads | 2 +- .../riot/src/test/resources/replicate-struct | 2 +- 82 files changed, 1677 insertions(+), 1400 deletions(-) delete mode 100644 core/riot-core/src/main/java/com/redis/riot/core/ExportProcessorOptions.java create mode 100644 core/riot-core/src/main/java/com/redis/riot/core/KeyValueProcessorOptions.java create mode 100644 core/riot-core/src/main/java/com/redis/riot/core/RiotVersion.java create mode 100644 core/riot-core/src/main/resources/com/redis/riot/core/RiotVersion.properties create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/AbstractMapImportCommand.java delete mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/AbstractStructImportCommand.java delete mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/BaseCommand.java rename plugins/riot/src/main/java/com/redis/riot/cli/{FileDumpExportCommand.java => FileExportCommand.java} (84%) create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/JobArgs.java rename plugins/riot/src/main/java/com/redis/riot/cli/{LoggingMixin.java => LoggingArgs.java} (94%) delete mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/ManifestVersionProvider.java delete mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/RedisArgs.java create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/RedisClientArgs.java rename plugins/riot/src/main/java/com/redis/riot/cli/{RedisCommand.java => RedisOperationCommand.java} (81%) create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/RedisURIArgs.java create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/ReplicateSourceArgs.java create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/ReplicateTargetArgs.java create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/SslArgs.java create mode 100644 plugins/riot/src/main/java/com/redis/riot/cli/Versions.java rename plugins/riot/src/main/java/com/redis/riot/cli/redis/{AbstractRedisCollectionCommand.java => AbstractCollectionOperationCommand.java} (90%) rename plugins/riot/src/main/java/com/redis/riot/cli/redis/{AbstractRedisCommand.java => AbstractRedisOperationCommand.java} (92%) create mode 100644 plugins/riot/src/main/resources/com/redis/riot/cli/Banner.properties create mode 100644 plugins/riot/src/main/resources/com/redis/riot/cli/banner.txt diff --git a/VERSION b/VERSION index b347b11ea..94ae9ee1f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.3 +4.0.0-SNAPSHOT diff --git a/connectors/riot-faker/src/main/java/com/redis/riot/faker/FakerItemReader.java b/connectors/riot-faker/src/main/java/com/redis/riot/faker/FakerItemReader.java index 39b467cb6..6d2dcac3e 100644 --- a/connectors/riot-faker/src/main/java/com/redis/riot/faker/FakerItemReader.java +++ b/connectors/riot-faker/src/main/java/com/redis/riot/faker/FakerItemReader.java @@ -31,10 +31,22 @@ public class FakerItemReader extends AbstractItemCountingItemStreamItemReader> writer() { WritableResource resource; try { diff --git a/connectors/riot-file/src/test/java/com/redis/riot/file/AbstractFileTests.java b/connectors/riot-file/src/test/java/com/redis/riot/file/AbstractFileTests.java index d84eb4507..1e01af73f 100644 --- a/connectors/riot-file/src/test/java/com/redis/riot/file/AbstractFileTests.java +++ b/connectors/riot-file/src/test/java/com/redis/riot/file/AbstractFileTests.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.TestInfo; import com.amazonaws.util.IOUtils; -import com.redis.riot.core.RedisClientOptions; import com.redis.riot.core.operation.HsetBuilder; import com.redis.spring.batch.test.AbstractTestBase; @@ -31,7 +30,8 @@ abstract class AbstractFileTests extends AbstractTestBase { @Test void fileImportJSON(TestInfo info) throws Exception { try (FileImport executable = new FileImport()) { - executable.setRedisClientOptions(redisClientOptions()); + executable.getRedisClientOptions().setRedisURI(redisURI); + executable.getRedisClientOptions().setCluster(getRedisServer().isRedisCluster()); executable.setFiles(BEERS_JSON_URL); HsetBuilder hsetBuilder = new HsetBuilder(); hsetBuilder.setKeyspace(KEYSPACE); @@ -52,18 +52,12 @@ void fileImportJSON(TestInfo info) throws Exception { Assertions.assertEquals("Hocus Pocus", beer1.get("name")); } - private RedisClientOptions redisClientOptions() { - RedisClientOptions options = new RedisClientOptions(); - options.setCluster(getRedisServer().isRedisCluster()); - options.setUri(getRedisServer().getRedisURI()); - return options; - } - @SuppressWarnings("unchecked") @Test void fileApiImportCSV(TestInfo info) throws Exception { try (FileImport executable = new FileImport()) { - executable.setRedisClientOptions(redisClientOptions()); + executable.getRedisClientOptions().setRedisURI(redisURI); + executable.getRedisClientOptions().setCluster(getRedisServer().isRedisCluster()); executable.setFiles("https://storage.googleapis.com/jrx/beers.csv"); executable.setHeader(true); executable.setName(name(info)); @@ -92,7 +86,8 @@ void fileApiFileExpansion(TestInfo info) throws Exception { File file2 = temp.resolve("beers2.csv").toFile(); IOUtils.copy(getClass().getClassLoader().getResourceAsStream("beers2.csv"), new FileOutputStream(file2)); try (FileImport executable = new FileImport()) { - executable.setRedisClientOptions(redisClientOptions()); + executable.getRedisClientOptions().setRedisURI(redisURI); + executable.getRedisClientOptions().setCluster(getRedisServer().isRedisCluster()); executable.setFiles(temp.resolve("*.csv").toFile().getPath()); executable.setHeader(true); executable.setName(name(info)); @@ -116,7 +111,8 @@ void fileApiFileExpansion(TestInfo info) throws Exception { @Test void fileImportCSVMultiThreaded(TestInfo info) throws Exception { try (FileImport executable = new FileImport()) { - executable.setRedisClientOptions(redisClientOptions()); + executable.getRedisClientOptions().setRedisURI(redisURI); + executable.getRedisClientOptions().setCluster(getRedisServer().isRedisCluster()); executable.setFiles("https://storage.googleapis.com/jrx/beers.csv"); executable.setHeader(true); executable.setThreads(3); @@ -141,7 +137,8 @@ void fileImportCSVMultiThreaded(TestInfo info) throws Exception { @Test void fileImportJSONL(TestInfo info) throws Exception { try (FileImport executable = new FileImport()) { - executable.setRedisClientOptions(redisClientOptions()); + executable.getRedisClientOptions().setRedisURI(redisURI); + executable.getRedisClientOptions().setCluster(getRedisServer().isRedisCluster()); executable.setFiles(BEERS_JSONL_URL); HsetBuilder hsetBuilder = new HsetBuilder(); hsetBuilder.setKeyspace(KEYSPACE); diff --git a/connectors/riot-redis/src/main/java/com/redis/riot/redis/Replication.java b/connectors/riot-redis/src/main/java/com/redis/riot/redis/Replication.java index b16662d62..d56a93281 100644 --- a/connectors/riot-redis/src/main/java/com/redis/riot/redis/Replication.java +++ b/connectors/riot-redis/src/main/java/com/redis/riot/redis/Replication.java @@ -37,7 +37,6 @@ import io.lettuce.core.AbstractRedisClient; import io.lettuce.core.ReadFrom; import io.lettuce.core.RedisException; -import io.lettuce.core.RedisURI; import io.lettuce.core.codec.ByteArrayCodec; public class Replication extends AbstractExport { @@ -74,26 +73,19 @@ public class Replication extends AbstractExport { private Duration idleTimeout = DEFAULT_IDLE_TIMEOUT; private int notificationQueueCapacity = DEFAULT_NOTIFICATION_QUEUE_CAPACITY; - private RedisURI targetRedisURI; private AbstractRedisClient targetRedisClient; - @Override - protected boolean isStruct() { - return type == ReplicationType.STRUCT; - } - @Override public void afterPropertiesSet() throws Exception { - targetRedisURI = targetRedisClientOptions.redisURI(); - targetRedisClient = targetRedisClientOptions.client(targetRedisURI); + targetRedisClient = targetRedisClientOptions.redisClient(); super.afterPropertiesSet(); } @Override protected StandardEvaluationContext evaluationContext() { StandardEvaluationContext context = super.evaluationContext(); - context.setVariable(SOURCE_VAR, redisURI); - context.setVariable(TARGET_VAR, targetRedisURI); + context.setVariable(SOURCE_VAR, getRedisClientOptions().getRedisURI()); + context.setVariable(TARGET_VAR, targetRedisClientOptions.getRedisURI()); return context; } @@ -139,7 +131,7 @@ protected Job job() { SimpleFlow replicateFlow = flow(FLOW_REPLICATE).split(new SimpleAsyncTaskExecutor()).add(liveFlow, scanFlow) .build(); JobFlowBuilder live = jobBuilder().start(replicateFlow); - if (shouldCompare()) { + if (shouldCompare() && processor == null) { live.next(compareStep); } return live.build().build(); @@ -148,7 +140,7 @@ protected Job job() { return jobBuilder().start(liveStep.build()).build(); case SNAPSHOT: SimpleJobBuilder snapshot = jobBuilder().start(scanStep.build()); - if (shouldCompare()) { + if (shouldCompare() && processor == null) { snapshot.next(compareStep); } return snapshot.build(); @@ -174,7 +166,7 @@ private FlowBuilder flow(String name) { } private boolean shouldCompare() { - return compareMode != CompareMode.NONE && !isDryRun() && getProcessorOptions().isEmpty(); + return compareMode != CompareMode.NONE && !isDryRun(); } @Override @@ -212,7 +204,7 @@ protected void configure(RedisItemReader reader) { @SuppressWarnings({ "unchecked", "rawtypes" }) private RedisItemReader> reader() { - if (isStruct()) { + if (type == ReplicationType.STRUCT) { return RedisItemReader.struct(ByteArrayCodec.INSTANCE); } return (RedisItemReader) RedisItemReader.dump(); @@ -252,7 +244,7 @@ protected void configure(RedisItemWriter writer) { } private RedisItemWriter> writer() { - if (isStruct()) { + if (type == ReplicationType.STRUCT) { return RedisItemWriter.struct(ByteArrayCodec.INSTANCE); } return RedisItemWriter.dump(); diff --git a/connectors/riot-redis/src/test/java/com/redis/riot/redis/ReplicationTests.java b/connectors/riot-redis/src/test/java/com/redis/riot/redis/ReplicationTests.java index 661cac4ce..cf3b17a89 100644 --- a/connectors/riot-redis/src/test/java/com/redis/riot/redis/ReplicationTests.java +++ b/connectors/riot-redis/src/test/java/com/redis/riot/redis/ReplicationTests.java @@ -14,7 +14,7 @@ import com.redis.lettucemod.api.StatefulRedisModulesConnection; import com.redis.lettucemod.util.RedisModulesUtils; -import com.redis.riot.core.ExportProcessorOptions; +import com.redis.riot.core.KeyValueProcessorOptions; import com.redis.riot.core.PredicateItemProcessor; import com.redis.riot.core.RedisClientOptions; import com.redis.riot.core.RiotUtils; @@ -26,6 +26,7 @@ import com.redis.spring.batch.util.Predicates; import com.redis.testcontainers.RedisServer; +import io.lettuce.core.RedisURI; import io.lettuce.core.cluster.SlotHash; import io.lettuce.core.codec.ByteArrayCodec; @@ -62,7 +63,7 @@ protected void execute(Replication replication, TestInfo info) throws Exception private RedisClientOptions redisOptions(RedisServer redis) { RedisClientOptions options = new RedisClientOptions(); - options.setUri(redis.getRedisURI()); + options.setRedisURI(RedisURI.create(redis.getRedisURI())); options.setCluster(redis.isRedisCluster()); return options; } @@ -88,8 +89,8 @@ void keyProcessor(TestInfo info) throws Throwable { Assertions.assertEquals(value1, targetRedisCommands.get("string:" + key1)); } - private ExportProcessorOptions processorOptions(String keyExpression) { - ExportProcessorOptions options = new ExportProcessorOptions(); + private KeyValueProcessorOptions processorOptions(String keyExpression) { + KeyValueProcessorOptions options = new KeyValueProcessorOptions(); options.setKeyExpression(RiotUtils.parseTemplate(keyExpression)); return options; } diff --git a/core/riot-core/riot-core.gradle b/core/riot-core/riot-core.gradle index fa888aa59..bd17a329c 100644 --- a/core/riot-core/riot-core.gradle +++ b/core/riot-core/riot-core.gradle @@ -23,7 +23,7 @@ dependencies { api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' implementation 'org.springframework.boot:spring-boot-autoconfigure' implementation 'org.hsqldb:hsqldb' - implementation 'org.apache.commons:commons-pool2' + implementation 'org.apache.commons:commons-pool2' testImplementation 'org.awaitility:awaitility' } @@ -37,4 +37,23 @@ if (!(project.findProperty('automatic.module.name.skip') ?: false).toBoolean()) attributes('Automatic-Module-Name': project.findProperty('automatic.module.name')) } } -} \ No newline at end of file +} + +project.rootProject.gradle.addBuildListener(new BuildAdapter() { + @Override + void projectsEvaluated(Gradle gradle) { + gradle.rootProject.subprojects + .find { p -> p.name == 'riot-core' } + .processResources { + inputs.property('build_date', gradle.rootProject.config.buildInfo.buildDate + ':' + gradle.rootProject.config.buildInfo.buildTime) + filesMatching(['**/RiotVersion.properties']) { + expand( + 'riot_version': gradle.rootProject.version, + 'build_date': gradle.rootProject.config.buildInfo.buildDate, + 'build_time': gradle.rootProject.config.buildInfo.buildTime, + 'build_revision': gradle.rootProject.config.buildInfo.buildRevision + ) + } + } + } +}) \ No newline at end of file diff --git a/core/riot-core/src/main/java/com/redis/riot/core/AbstractExport.java b/core/riot-core/src/main/java/com/redis/riot/core/AbstractExport.java index 0fa3960b5..fb63f6858 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/AbstractExport.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/AbstractExport.java @@ -1,108 +1,24 @@ package com.redis.riot.core; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; +import org.springframework.batch.item.ItemProcessor; -import org.springframework.batch.item.function.FunctionItemProcessor; -import org.springframework.util.CollectionUtils; - -import com.redis.riot.core.function.CompositeOperator; -import com.redis.riot.core.function.DropStreamMessageId; -import com.redis.riot.core.function.ExpressionFunction; -import com.redis.riot.core.function.LongExpressionFunction; -import com.redis.riot.core.function.StringKeyValueFunction; -import com.redis.riot.core.function.ToStringKeyValueFunction; import com.redis.spring.batch.KeyValue; import com.redis.spring.batch.RedisItemReader; -import com.redis.spring.batch.util.BatchUtils; -import com.redis.spring.batch.util.Predicates; import io.lettuce.core.codec.RedisCodec; public abstract class AbstractExport extends AbstractRedisCallable { private RedisReaderOptions readerOptions = new RedisReaderOptions(); - private KeyFilterOptions keyFilterOptions = new KeyFilterOptions(); - private ExportProcessorOptions processorOptions = new ExportProcessorOptions(); - - protected FunctionItemProcessor, KeyValue> processor(RedisCodec codec) { - if (processorOptions.isEmpty()) { - return null; - } - Function, KeyValue> code = new ToStringKeyValueFunction<>(codec); - Function, KeyValue> decode = new StringKeyValueFunction<>(codec); - CompositeOperator> operator = new CompositeOperator<>(processorConsumers()); - return new FunctionItemProcessor<>(code.andThen(operator).andThen(decode)); - } - - private List>> processorConsumers() { - List>> consumers = new ArrayList<>(); - if (processorOptions.getKeyExpression() != null) { - ExpressionFunction function = expressionFunction( - processorOptions.getKeyExpression().getExpression()); - consumers.add(t -> t.setKey(function.apply(t))); - } - if (processorOptions.isDropTtl()) { - consumers.add(t -> t.setTtl(0)); - } - if (processorOptions.getTtlExpression() != null) { - LongExpressionFunction function = longExpressionFunction(processorOptions.getTtlExpression()); - consumers.add(t -> t.setTtl(function.applyAsLong(t))); - } - if (processorOptions.isDropStreamMessageId() && isStruct()) { - consumers.add(new DropStreamMessageId()); - } - if (processorOptions.getTypeExpression() != null) { - ExpressionFunction, String> function = expressionFunction( - processorOptions.getTypeExpression()); - consumers.add(t -> t.setType(function.apply(t))); - } - return consumers; - } - - protected abstract boolean isStruct(); + private KeyValueProcessorOptions processorOptions = new KeyValueProcessorOptions(); @Override protected void configure(RedisItemReader reader) { reader.setJobFactory(getJobFactory()); - reader.setDatabase(redisURI.getDatabase()); - if (!keyFilterOptions.isEmpty()) { - Predicate predicate = keyFilterPredicate(reader.getCodec()); - reader.setKeyProcessor(new PredicateItemProcessor<>(predicate)); - } readerOptions.configure(reader); super.configure(reader); } - public Predicate keyFilterPredicate(RedisCodec codec) { - return slotsPredicate(codec).and(globPredicate(codec)); - } - - private Predicate slotsPredicate(RedisCodec codec) { - if (CollectionUtils.isEmpty(keyFilterOptions.getSlots())) { - return Predicates.isTrue(); - } - Stream> predicates = keyFilterOptions.getSlots().stream() - .map(r -> Predicates.slotRange(codec, r.getStart(), r.getEnd())); - return Predicates.or(predicates); - } - - private Predicate globPredicate(RedisCodec codec) { - return Predicates.map(BatchUtils.toStringKeyFunction(codec), globPredicate()); - } - - private Predicate globPredicate() { - Predicate include = RiotUtils.globPredicate(keyFilterOptions.getIncludes()); - if (CollectionUtils.isEmpty(keyFilterOptions.getExcludes())) { - return include; - } - return include.and(RiotUtils.globPredicate(keyFilterOptions.getExcludes()).negate()); - } - public RedisReaderOptions getReaderOptions() { return readerOptions; } @@ -111,20 +27,16 @@ public void setReaderOptions(RedisReaderOptions options) { this.readerOptions = options; } - public ExportProcessorOptions getProcessorOptions() { + public KeyValueProcessorOptions getProcessorOptions() { return processorOptions; } - public void setProcessorOptions(ExportProcessorOptions options) { + public void setProcessorOptions(KeyValueProcessorOptions options) { this.processorOptions = options; } - public KeyFilterOptions getKeyFilterOptions() { - return keyFilterOptions; - } - - public void setKeyFilterOptions(KeyFilterOptions options) { - this.keyFilterOptions = options; + public ItemProcessor, KeyValue> processor(RedisCodec codec) { + return processorOptions.processor(getEvaluationContext(), codec); } } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/AbstractMapExport.java b/core/riot-core/src/main/java/com/redis/riot/core/AbstractMapExport.java index 6894c1dd4..139fb989a 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/AbstractMapExport.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/AbstractMapExport.java @@ -45,11 +45,6 @@ protected ItemProcessor, Map> processor return RiotUtils.processor(processor, new FunctionItemProcessor<>(toMapFunction)); } - @Override - protected boolean isStruct() { - return true; - } - protected abstract ItemWriter> writer(); } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/AbstractRedisCallable.java b/core/riot-core/src/main/java/com/redis/riot/core/AbstractRedisCallable.java index 0db6a47d3..fa69d4cec 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/AbstractRedisCallable.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/AbstractRedisCallable.java @@ -1,27 +1,23 @@ package com.redis.riot.core; -import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; import com.redis.lettucemod.api.StatefulRedisModulesConnection; import com.redis.lettucemod.api.sync.RedisModulesCommands; import com.redis.lettucemod.util.RedisModulesUtils; -import com.redis.riot.core.function.ExpressionFunction; -import com.redis.riot.core.function.LongExpressionFunction; import com.redis.spring.batch.RedisItemReader; import com.redis.spring.batch.RedisItemWriter; import io.lettuce.core.AbstractRedisClient; -import io.lettuce.core.RedisURI; public abstract class AbstractRedisCallable extends AbstractRiotCallable { private static final String CONTEXT_VAR_REDIS = "redis"; - private RedisClientOptions redisClientOptions = new RedisClientOptions(); private EvaluationContextOptions evaluationContextOptions = new EvaluationContextOptions(); - protected RedisURI redisURI; + private RedisClientOptions redisClientOptions = new RedisClientOptions(); + private AbstractRedisClient redisClient; private StatefulRedisModulesConnection redisConnection; protected RedisModulesCommands redisCommands; @@ -29,8 +25,7 @@ public abstract class AbstractRedisCallable extends AbstractRiotCallable { @Override public void afterPropertiesSet() throws Exception { - redisURI = redisClientOptions.redisURI(); - redisClient = redisClientOptions.client(redisURI); + redisClient = redisClientOptions.redisClient(); redisConnection = RedisModulesUtils.connection(redisClient); redisCommands = redisConnection.sync(); evaluationContext = evaluationContext(); @@ -57,14 +52,6 @@ public void close() { } } - public RedisClientOptions getRedisClientOptions() { - return redisClientOptions; - } - - public void setRedisClientOptions(RedisClientOptions options) { - this.redisClientOptions = options; - } - public StandardEvaluationContext getEvaluationContext() { return evaluationContext; } @@ -75,18 +62,19 @@ public void setEvaluationContextOptions(EvaluationContextOptions spelProcessorOp protected void configure(RedisItemReader reader) { reader.setClient(redisClient); + reader.setDatabase(redisClientOptions.getRedisURI().getDatabase()); } protected void configure(RedisItemWriter writer) { writer.setClient(redisClient); } - protected ExpressionFunction expressionFunction(Expression expression) { - return new ExpressionFunction<>(evaluationContext, expression, String.class); + public RedisClientOptions getRedisClientOptions() { + return redisClientOptions; } - protected LongExpressionFunction longExpressionFunction(Expression expression) { - return new LongExpressionFunction<>(evaluationContext, expression); + public void setRedisClientOptions(RedisClientOptions options) { + this.redisClientOptions = options; } } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/ExportProcessorOptions.java b/core/riot-core/src/main/java/com/redis/riot/core/ExportProcessorOptions.java deleted file mode 100644 index b23b0fe1a..000000000 --- a/core/riot-core/src/main/java/com/redis/riot/core/ExportProcessorOptions.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.redis.riot.core; - -import org.springframework.expression.Expression; - -public class ExportProcessorOptions { - - private TemplateExpression keyExpression; - private Expression ttlExpression; - private boolean dropTtl; - private Expression typeExpression; - private boolean dropStreamMessageId; - - public boolean isDropStreamMessageId() { - return dropStreamMessageId; - } - - public void setDropStreamMessageId(boolean dropStreamMessageId) { - this.dropStreamMessageId = dropStreamMessageId; - } - - public Expression getTypeExpression() { - return typeExpression; - } - - public void setTypeExpression(Expression expression) { - this.typeExpression = expression; - } - - public boolean isDropTtl() { - return dropTtl; - } - - public void setDropTtl(boolean dropTtl) { - this.dropTtl = dropTtl; - } - - public TemplateExpression getKeyExpression() { - return keyExpression; - } - - public void setKeyExpression(TemplateExpression expression) { - this.keyExpression = expression; - } - - public Expression getTtlExpression() { - return ttlExpression; - } - - public void setTtlExpression(Expression expression) { - this.ttlExpression = expression; - } - - public boolean isEmpty() { - return keyExpression == null && ttlExpression == null && !dropTtl && typeExpression == null - && !dropStreamMessageId; - } - -} diff --git a/core/riot-core/src/main/java/com/redis/riot/core/KeyFilterOptions.java b/core/riot-core/src/main/java/com/redis/riot/core/KeyFilterOptions.java index 87cebff80..f3b6bf102 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/KeyFilterOptions.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/KeyFilterOptions.java @@ -1,9 +1,17 @@ package com.redis.riot.core; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; +import org.springframework.batch.item.ItemProcessor; import org.springframework.util.CollectionUtils; +import com.redis.spring.batch.util.BatchUtils; +import com.redis.spring.batch.util.Predicates; + +import io.lettuce.core.codec.RedisCodec; + public class KeyFilterOptions { private List includes; @@ -38,4 +46,36 @@ public boolean isEmpty() { return CollectionUtils.isEmpty(includes) && CollectionUtils.isEmpty(excludes) && CollectionUtils.isEmpty(slots); } + public Predicate predicate(RedisCodec codec) { + return slotsPredicate(codec).and(globPredicate(codec)); + } + + private Predicate slotsPredicate(RedisCodec codec) { + if (CollectionUtils.isEmpty(slots)) { + return Predicates.isTrue(); + } + Stream> predicates = slots.stream() + .map(r -> Predicates.slotRange(codec, r.getStart(), r.getEnd())); + return Predicates.or(predicates); + } + + private Predicate globPredicate(RedisCodec codec) { + return Predicates.map(BatchUtils.toStringKeyFunction(codec), globPredicate()); + } + + private Predicate globPredicate() { + Predicate include = RiotUtils.globPredicate(includes); + if (CollectionUtils.isEmpty(excludes)) { + return include; + } + return include.and(RiotUtils.globPredicate(excludes).negate()); + } + + public ItemProcessor processor(RedisCodec codec) { + if (isEmpty()) { + return null; + } + return new PredicateItemProcessor<>(predicate(codec)); + } + } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/KeyValueProcessorOptions.java b/core/riot-core/src/main/java/com/redis/riot/core/KeyValueProcessorOptions.java new file mode 100644 index 000000000..69f82f01f --- /dev/null +++ b/core/riot-core/src/main/java/com/redis/riot/core/KeyValueProcessorOptions.java @@ -0,0 +1,108 @@ +package com.redis.riot.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.ToLongFunction; + +import org.springframework.batch.item.function.FunctionItemProcessor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; + +import com.redis.riot.core.function.CompositeOperator; +import com.redis.riot.core.function.DropStreamMessageId; +import com.redis.riot.core.function.ExpressionFunction; +import com.redis.riot.core.function.LongExpressionFunction; +import com.redis.riot.core.function.StringKeyValueFunction; +import com.redis.riot.core.function.ToStringKeyValueFunction; +import com.redis.spring.batch.KeyValue; + +import io.lettuce.core.codec.RedisCodec; + +public class KeyValueProcessorOptions { + + private TemplateExpression keyExpression; + private Expression ttlExpression; + private boolean dropTtl; + private Expression typeExpression; + private boolean dropStreamMessageId; + + public FunctionItemProcessor, KeyValue> processor(EvaluationContext ctx, + RedisCodec codec) { + if (keyExpression == null && ttlExpression == null && !dropTtl && typeExpression == null + && !dropStreamMessageId) { + return null; + } + Function, KeyValue> code = new ToStringKeyValueFunction<>(codec); + Function, KeyValue> decode = new StringKeyValueFunction<>(codec); + CompositeOperator> operator = new CompositeOperator<>(processorConsumers(ctx)); + return new FunctionItemProcessor<>(code.andThen(operator).andThen(decode)); + } + + private List>> processorConsumers(EvaluationContext context) { + List>> consumers = new ArrayList<>(); + if (keyExpression != null) { + ExpressionFunction function = new ExpressionFunction<>(context, + keyExpression.getExpression(), String.class); + consumers.add(t -> t.setKey(function.apply(t))); + } + if (dropTtl) { + consumers.add(t -> t.setTtl(0)); + } + if (ttlExpression != null) { + ToLongFunction> function = new LongExpressionFunction<>(context, ttlExpression); + consumers.add(t -> t.setTtl(function.applyAsLong(t))); + } + if (dropStreamMessageId) { + consumers.add(new DropStreamMessageId()); + } + if (typeExpression != null) { + Function, String> function = new ExpressionFunction<>(context, typeExpression, + String.class); + consumers.add(t -> t.setType(function.apply(t))); + } + return consumers; + } + + public boolean isDropStreamMessageId() { + return dropStreamMessageId; + } + + public void setDropStreamMessageId(boolean dropStreamMessageId) { + this.dropStreamMessageId = dropStreamMessageId; + } + + public Expression getTypeExpression() { + return typeExpression; + } + + public void setTypeExpression(Expression expression) { + this.typeExpression = expression; + } + + public boolean isDropTtl() { + return dropTtl; + } + + public void setDropTtl(boolean dropTtl) { + this.dropTtl = dropTtl; + } + + public TemplateExpression getKeyExpression() { + return keyExpression; + } + + public void setKeyExpression(TemplateExpression expression) { + this.keyExpression = expression; + } + + public Expression getTtlExpression() { + return ttlExpression; + } + + public void setTtlExpression(Expression expression) { + this.ttlExpression = expression; + } + +} diff --git a/core/riot-core/src/main/java/com/redis/riot/core/RedisClientOptions.java b/core/riot-core/src/main/java/com/redis/riot/core/RedisClientOptions.java index 951e7708f..e249c8193 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/RedisClientOptions.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/RedisClientOptions.java @@ -1,316 +1,60 @@ package com.redis.riot.core; -import java.io.File; -import java.time.Duration; - -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - import com.redis.lettucemod.RedisModulesClient; import com.redis.lettucemod.cluster.RedisModulesClusterClient; import io.lettuce.core.AbstractRedisClient; import io.lettuce.core.ClientOptions; import io.lettuce.core.RedisURI; -import io.lettuce.core.SslOptions; -import io.lettuce.core.SslOptions.Builder; -import io.lettuce.core.SslOptions.Resource; -import io.lettuce.core.SslVerifyMode; import io.lettuce.core.cluster.ClusterClientOptions; -import io.lettuce.core.event.DefaultEventPublisherOptions; -import io.lettuce.core.event.EventPublisherOptions; -import io.lettuce.core.metrics.CommandLatencyCollector; -import io.lettuce.core.metrics.CommandLatencyRecorder; -import io.lettuce.core.metrics.DefaultCommandLatencyCollectorOptions; -import io.lettuce.core.protocol.ProtocolVersion; import io.lettuce.core.resource.ClientResources; -import io.lettuce.core.resource.DefaultClientResources; public class RedisClientOptions { - public static final String DEFAULT_HOST = "127.0.0.1"; - public static final int DEFAULT_PORT = 6379; - public static final SslVerifyMode DEFAULT_VERIFY_PEER = SslVerifyMode.FULL; - public static final Duration DEFAULT_TIMEOUT = RedisURI.DEFAULT_TIMEOUT_DURATION; + public static final String DEFAULT_REDIS_HOST = "127.0.0.1"; + public static final int DEFAULT_REDIS_PORT = RedisURI.DEFAULT_REDIS_PORT; + public static final RedisURI DEFAULT_REDIS_URI = RedisURI.create(DEFAULT_REDIS_HOST, DEFAULT_REDIS_PORT); - private String uri; - private String host = DEFAULT_HOST; - private int port = DEFAULT_PORT; - private String socket; - private String username; - private char[] password; - private Duration timeout = DEFAULT_TIMEOUT; - private int database; - private boolean tls; - private String clientName; - private SslVerifyMode verifyPeer = DEFAULT_VERIFY_PEER; + private RedisURI redisURI = DEFAULT_REDIS_URI; private boolean cluster; - private Duration metricsStep; - private boolean autoReconnect = ClientOptions.DEFAULT_AUTO_RECONNECT; - private ProtocolVersion protocolVersion; - private File keystore; - private char[] keystorePassword; - private File truststore; - private char[] truststorePassword; - private File keyCert; - private File key; - private char[] keyPassword; - private File trustedCerts; + private ClientOptions options; + private ClientResources resources; - public AbstractRedisClient client(RedisURI redisURI) { - ClientResources resources = clientResources(); + public AbstractRedisClient redisClient() { if (cluster) { - RedisModulesClusterClient client = RedisModulesClusterClient.create(resources, redisURI); - client.setOptions(clientOptions(ClusterClientOptions.builder()).build()); + RedisModulesClusterClient client = clusterClient(); + if (options != null) { + client.setOptions((ClusterClientOptions) options); + } return client; } - RedisModulesClient client = RedisModulesClient.create(resources, redisURI); - client.setOptions(clientOptions(ClientOptions.builder()).build()); - return client; - } - - private B clientOptions(B builder) { - builder.autoReconnect(autoReconnect); - builder.sslOptions(sslOptions()); - builder.protocolVersion(protocolVersion); - return builder; - } - - public SslOptions sslOptions() { - Builder ssl = SslOptions.builder(); - if (key != null) { - ssl.keyManager(keyCert, key, keyPassword); - } - if (keystore != null) { - ssl.keystore(keystore, keystorePassword); - } - if (truststore != null) { - ssl.truststore(Resource.from(truststore), truststorePassword); - } - if (trustedCerts != null) { - ssl.trustManager(trustedCerts); - } - return ssl.build(); - } - - private ClientResources clientResources() { - DefaultClientResources.Builder builder = DefaultClientResources.builder(); - if (metricsStep != null) { - builder.commandLatencyRecorder(commandLatencyRecorder()); - builder.commandLatencyPublisherOptions(commandLatencyPublisherOptions(metricsStep)); + RedisModulesClient client = client(); + if (options != null) { + client.setOptions(options); } - return builder.build(); - } - - private EventPublisherOptions commandLatencyPublisherOptions(Duration step) { - return DefaultEventPublisherOptions.builder().eventEmitInterval(step).build(); - } - - private CommandLatencyRecorder commandLatencyRecorder() { - return CommandLatencyCollector.create(DefaultCommandLatencyCollectorOptions.builder().enable().build()); + return client; } - public RedisURI redisURI() { - RedisURI.Builder builder = redisURIBuilder(); - if (database > 0) { - builder.withDatabase(database); - } - if (StringUtils.hasLength(clientName)) { - builder.withClientName(clientName); - } - if (!ObjectUtils.isEmpty(password)) { - if (StringUtils.hasLength(username)) { - builder.withAuthentication(username, password); - } else { - builder.withPassword(password); - } - } - if (tls) { - builder.withSsl(tls); - builder.withVerifyPeer(verifyPeer); + private RedisModulesClient client() { + if (resources == null) { + return RedisModulesClient.create(redisURI); } - if (timeout != null) { - builder.withTimeout(timeout); - } - return builder.build(); + return RedisModulesClient.create(resources, redisURI); } - private RedisURI.Builder redisURIBuilder() { - if (StringUtils.hasLength(uri)) { - return RedisURI.builder(RedisURI.create(uri)); - } - if (StringUtils.hasLength(socket)) { - return RedisURI.Builder.socket(socket); + private RedisModulesClusterClient clusterClient() { + if (resources == null) { + return RedisModulesClusterClient.create(redisURI); } - return RedisURI.Builder.redis(host, port); - } - - public String getClientName() { - return clientName; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getUri() { - return uri; - } - - public void setUri(String uri) { - this.uri = uri; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public String getSocket() { - return socket; - } - - public void setSocket(String socket) { - this.socket = socket; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public char[] getPassword() { - return password; - } - - public void setPassword(char[] password) { - this.password = password; - } - - public Duration getTimeout() { - return timeout; + return RedisModulesClusterClient.create(resources, redisURI); } - public void setTimeout(Duration timeout) { - this.timeout = timeout; + public RedisURI getRedisURI() { + return redisURI; } - public int getDatabase() { - return database; - } - - public void setDatabase(int database) { - this.database = database; - } - - public boolean isTls() { - return tls; - } - - public void setTls(boolean tls) { - this.tls = tls; - } - - public SslVerifyMode getVerifyPeer() { - return verifyPeer; - } - - public void setVerifyPeer(SslVerifyMode mode) { - this.verifyPeer = mode; - } - - public File getKeystore() { - return keystore; - } - - public void setKeystore(File keystore) { - this.keystore = keystore; - } - - public char[] getKeystorePassword() { - return keystorePassword; - } - - public void setKeystorePassword(char[] keystorePassword) { - this.keystorePassword = keystorePassword; - } - - public File getTruststore() { - return truststore; - } - - public void setTruststore(File truststore) { - this.truststore = truststore; - } - - public char[] getTruststorePassword() { - return truststorePassword; - } - - public void setTruststorePassword(char[] truststorePassword) { - this.truststorePassword = truststorePassword; - } - - public File getKeyCert() { - return keyCert; - } - - public void setKeyCert(File keyCert) { - this.keyCert = keyCert; - } - - public File getKey() { - return key; - } - - public void setKey(File key) { - this.key = key; - } - - public char[] getKeyPassword() { - return keyPassword; - } - - public void setKeyPassword(char[] keyPassword) { - this.keyPassword = keyPassword; - } - - public File getTrustedCerts() { - return trustedCerts; - } - - public void setTrustedCerts(File trustedCerts) { - this.trustedCerts = trustedCerts; - } - - public boolean isAutoReconnect() { - return autoReconnect; - } - - public void setAutoReconnect(boolean autoReconnect) { - this.autoReconnect = autoReconnect; - } - - public ProtocolVersion getProtocolVersion() { - return protocolVersion; - } - - public void setProtocolVersion(ProtocolVersion protocolVersion) { - this.protocolVersion = protocolVersion; + public void setRedisURI(RedisURI uri) { + this.redisURI = uri; } public boolean isCluster() { @@ -321,12 +65,20 @@ public void setCluster(boolean cluster) { this.cluster = cluster; } - public Duration getMetricsStep() { - return metricsStep; + public ClientOptions getOptions() { + return options; + } + + public void setOptions(ClientOptions options) { + this.options = options; + } + + public ClientResources getResources() { + return resources; } - public void setMetricsStep(Duration metricsStep) { - this.metricsStep = metricsStep; + public void setResources(ClientResources resources) { + this.resources = resources; } } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/RedisReaderOptions.java b/core/riot-core/src/main/java/com/redis/riot/core/RedisReaderOptions.java index 45afd8574..65549385c 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/RedisReaderOptions.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/RedisReaderOptions.java @@ -32,6 +32,25 @@ public class RedisReaderOptions { private ReadFrom readFrom; private DataSize memoryUsageLimit = DEFAULT_MEMORY_USAGE_LIMIT; private int memoryUsageSamples = DEFAULT_MEMORY_USAGE_SAMPLES; + private KeyFilterOptions keyFilterOptions = new KeyFilterOptions(); + + public void configure(RedisItemReader reader) { + reader.setChunkSize(chunkSize); + reader.setKeyPattern(keyPattern); + reader.setKeyType(keyType); + reader.setPollTimeout(pollTimeout); + reader.setQueueCapacity(queueCapacity); + reader.setReadFrom(readFrom); + reader.setScanCount(scanCount); + reader.setThreads(threads); + if (reader.getOperation() instanceof KeyValueRead) { + KeyValueRead operation = (KeyValueRead) reader.getOperation(); + operation.setMemUsageLimit(memoryUsageLimit); + operation.setMemUsageSamples(memoryUsageSamples); + } + reader.setPoolSize(poolSize); + reader.setKeyProcessor(keyFilterOptions.processor(reader.getCodec())); + } public String getKeyPattern() { return keyPattern; @@ -121,21 +140,12 @@ public void setMemoryUsageSamples(int memoryUsageSamples) { this.memoryUsageSamples = memoryUsageSamples; } - public void configure(RedisItemReader reader) { - reader.setChunkSize(chunkSize); - reader.setKeyPattern(keyPattern); - reader.setKeyType(keyType); - reader.setPollTimeout(pollTimeout); - reader.setQueueCapacity(queueCapacity); - reader.setReadFrom(readFrom); - reader.setScanCount(scanCount); - reader.setThreads(threads); - if (reader.getOperation() instanceof KeyValueRead) { - KeyValueRead operation = (KeyValueRead) reader.getOperation(); - operation.setMemUsageLimit(memoryUsageLimit); - operation.setMemUsageSamples(memoryUsageSamples); - } - reader.setPoolSize(poolSize); + public KeyFilterOptions getKeyFilterOptions() { + return keyFilterOptions; + } + public void setKeyFilterOptions(KeyFilterOptions options) { + this.keyFilterOptions = options; } + } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/RiotUtils.java b/core/riot-core/src/main/java/com/redis/riot/core/RiotUtils.java index 8fea70e24..a91111f15 100644 --- a/core/riot-core/src/main/java/com/redis/riot/core/RiotUtils.java +++ b/core/riot-core/src/main/java/com/redis/riot/core/RiotUtils.java @@ -1,5 +1,14 @@ package com.redis.riot.core; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -87,4 +96,32 @@ public static boolean isPositive(Duration duration) { return duration != null && !duration.isNegative() && !duration.isZero(); } + public static PrintStream newPrintStream(OutputStream out) { + return newPrintStream(out, true); + } + + public static PrintStream newPrintStream(OutputStream out, boolean autoFlush) { + try { + return new PrintStream(out, autoFlush, UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + } + + public static PrintWriter newPrintWriter(OutputStream out) { + return newPrintWriter(out, true); + } + + public static PrintWriter newPrintWriter(OutputStream out, boolean autoFlush) { + return new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, UTF_8)), autoFlush); + } + + public static String toString(ByteArrayOutputStream out) { + try { + return out.toString(UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + } + } diff --git a/core/riot-core/src/main/java/com/redis/riot/core/RiotVersion.java b/core/riot-core/src/main/java/com/redis/riot/core/RiotVersion.java new file mode 100644 index 000000000..cfe47b509 --- /dev/null +++ b/core/riot-core/src/main/java/com/redis/riot/core/RiotVersion.java @@ -0,0 +1,59 @@ +package com.redis.riot.core; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.ResourceBundle; + +public class RiotVersion { + + private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(RiotVersion.class.getName()); + private static final String RIOT_VERSION = BUNDLE.getString("riot_version"); + private static final String BUILD_DATE = BUNDLE.getString("build_date"); + private static final String BUILD_TIME = BUNDLE.getString("build_time"); + private static final String BUILD_REVISION = BUNDLE.getString("build_revision"); + private static final String SEPARATOR = "------------------------------------------------------------%n"; + private static final String RIOT_FORMAT = "riot %s%n"; + private static final String RIOT_VERSION_FORMAT = "RIOT%s"; + + private RiotVersion() { + // noop + } + + public static String getPlainVersion() { + return RIOT_VERSION; + } + + public static String riotVersion() { + return String.format(RIOT_VERSION_FORMAT, RIOT_VERSION); + } + + public static void banner(PrintStream out) { + banner(out, true); + } + + public static void banner(PrintStream out, boolean full) { + banner(RiotUtils.newPrintWriter(out), full); + } + + public static void banner(PrintWriter out) { + banner(out, true); + } + + public static void banner(PrintWriter out, boolean full) { + if (full) { + out.printf(SEPARATOR); + out.printf(RIOT_FORMAT, RIOT_VERSION); + + String jvm = System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + " " + + System.getProperty("java.vm.version") + ")"; + + out.printf(SEPARATOR); + out.printf("Build time: %s %s%n", BUILD_DATE, BUILD_TIME); + out.printf("Revision: %s%n", BUILD_REVISION); + out.printf("JVM: %s%n", jvm); + out.printf(SEPARATOR); + } else { + out.printf(RIOT_FORMAT, RIOT_VERSION); + } + } +} \ No newline at end of file diff --git a/core/riot-core/src/main/resources/com/redis/riot/core/RiotVersion.properties b/core/riot-core/src/main/resources/com/redis/riot/core/RiotVersion.properties new file mode 100644 index 000000000..3d19a8f90 --- /dev/null +++ b/core/riot-core/src/main/resources/com/redis/riot/core/RiotVersion.properties @@ -0,0 +1,4 @@ +riot_version=${riot_version} +build_date=${build_date} +build_time=${build_time} +build_revision=${build_revision} \ No newline at end of file diff --git a/core/riot-core/src/test/java/com/redis/riot/core/ProcessorTests.java b/core/riot-core/src/test/java/com/redis/riot/core/ProcessorTests.java index 85ac470bd..64a9ce0a4 100644 --- a/core/riot-core/src/test/java/com/redis/riot/core/ProcessorTests.java +++ b/core/riot-core/src/test/java/com/redis/riot/core/ProcessorTests.java @@ -10,7 +10,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.Job; import org.springframework.batch.item.ItemProcessor; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -19,31 +18,14 @@ class ProcessorTests { - private static class TestExport extends AbstractExport { - - @Override - protected boolean isStruct() { - return false; - } - - @Override - protected Job job() { - return null; - } - - } - @Test void keyFilter() { KeyFilterOptions options = new KeyFilterOptions(); options.setIncludes(Arrays.asList("foo*", "bar*")); - try (TestExport export = new TestExport()) { - export.setKeyFilterOptions(options); - Predicate predicate = export.keyFilterPredicate(StringCodec.UTF8); - Assertions.assertTrue(predicate.test("foobar")); - Assertions.assertTrue(predicate.test("barfoo")); - Assertions.assertFalse(predicate.test("key")); - } + Predicate predicate = options.predicate(StringCodec.UTF8); + Assertions.assertTrue(predicate.test("foobar")); + Assertions.assertTrue(predicate.test("barfoo")); + Assertions.assertFalse(predicate.test("key")); } @Test diff --git a/gradle.properties b/gradle.properties index a462dd72d..880ed8dd4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -38,7 +38,7 @@ latencyutilsVersion = 2.0.3 lettucemodVersion = 3.7.3 picocliVersion = 4.7.5 progressbarVersion = 0.10.0 -springBatchRedisVersion = 4.2.0 +springBatchRedisVersion = 4.2.1-SNAPSHOT testcontainersRedisVersion = 2.2.0 org.gradle.daemon = false diff --git a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractExportCommand.java b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractExportCommand.java index 7f7e63369..a501c1b47 100644 --- a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractExportCommand.java +++ b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractExportCommand.java @@ -1,74 +1,68 @@ package com.redis.riot.cli; -import java.util.function.LongSupplier; - -import org.springframework.batch.item.ItemReader; - import com.redis.riot.core.AbstractExport; -import com.redis.riot.core.AbstractRedisCallable; -import com.redis.spring.batch.RedisItemReader; -import com.redis.spring.batch.RedisItemReader.ReaderMode; -import com.redis.spring.batch.reader.ScanSizeEstimator; import picocli.CommandLine.ArgGroup; public abstract class AbstractExportCommand extends AbstractRiotCommand { - @ArgGroup(exclusive = false) - private RedisReaderArgs readerArgs = new RedisReaderArgs(); + @ArgGroup(exclusive = false, heading = "Redis reader options%n") + private ExportArgs exportArgs = new ExportArgs(); @ArgGroup(exclusive = false) private KeyValueProcessorArgs processorArgs = new KeyValueProcessorArgs(); - @ArgGroup(exclusive = false) - private KeyFilterArgs keyFilterArgs = new KeyFilterArgs(); - @Override - protected AbstractRedisCallable runnable() { - AbstractExport export = exportRunnable(); - export.setReaderOptions(readerArgs.readerOptions()); + protected AbstractExport callable() { + AbstractExport export = exportCallable(); + export.setRedisClientOptions(exportArgs.getRedisClientArgs().redisClientOptions()); + export.setReaderOptions(exportArgs.getRedisReaderArgs().redisReaderOptions()); export.setProcessorOptions(processorArgs.processorOptions()); - export.setKeyFilterOptions(keyFilterArgs.keyFilterOptions()); return export; } - protected abstract AbstractExport exportRunnable(); + protected abstract AbstractExport exportCallable(); - @Override - protected LongSupplier initialMaxSupplier(String stepName, ItemReader reader) { - if (((RedisItemReader) reader).getMode() == ReaderMode.LIVE) { - return () -> ProgressStepExecutionListener.UNKNOWN_SIZE; + public static class ExportArgs { + + @ArgGroup(exclusive = false) + private RedisClientArgs redisClientArgs = new RedisClientArgs(); + + @ArgGroup(exclusive = false) + private RedisReaderArgs redisReaderArgs = new RedisReaderArgs(); + + public RedisClientArgs getRedisClientArgs() { + return redisClientArgs; } - ScanSizeEstimator estimator = new ScanSizeEstimator(((RedisItemReader) reader).getClient()); - estimator.setKeyPattern(readerArgs.getScanMatch()); - if (readerArgs.getScanType() != null) { - estimator.setKeyType(readerArgs.getScanType()); + + public void setRedisClientArgs(RedisClientArgs redisClientArgs) { + this.redisClientArgs = redisClientArgs; } - return estimator; - } - public RedisReaderArgs getReaderArgs() { - return readerArgs; - } + public RedisReaderArgs getRedisReaderArgs() { + return redisReaderArgs; + } + + public void setRedisReaderArgs(RedisReaderArgs redisReaderArgs) { + this.redisReaderArgs = redisReaderArgs; + } - public void setReaderArgs(RedisReaderArgs readerArgs) { - this.readerArgs = readerArgs; } public KeyValueProcessorArgs getProcessorArgs() { return processorArgs; } - public void setProcessorArgs(KeyValueProcessorArgs processorArgs) { - this.processorArgs = processorArgs; + public void setProcessorArgs(KeyValueProcessorArgs args) { + this.processorArgs = args; } - public KeyFilterArgs getKeyFilterArgs() { - return keyFilterArgs; + public ExportArgs getExportArgs() { + return exportArgs; } - public void setKeyFilterArgs(KeyFilterArgs keyFilterArgs) { - this.keyFilterArgs = keyFilterArgs; + public void setExportArgs(ExportArgs exportArgs) { + this.exportArgs = exportArgs; } } diff --git a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractImportCommand.java b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractImportCommand.java index 5dd649e90..73322cbc4 100644 --- a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractImportCommand.java +++ b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractImportCommand.java @@ -1,86 +1,56 @@ package com.redis.riot.cli; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.expression.Expression; - -import com.redis.riot.cli.redis.DelCommand; -import com.redis.riot.cli.redis.ExpireCommand; -import com.redis.riot.cli.redis.GeoaddCommand; -import com.redis.riot.cli.redis.HsetCommand; -import com.redis.riot.cli.redis.JsonSetCommand; -import com.redis.riot.cli.redis.LpushCommand; -import com.redis.riot.cli.redis.RpushCommand; -import com.redis.riot.cli.redis.SaddCommand; -import com.redis.riot.cli.redis.SetCommand; -import com.redis.riot.cli.redis.SugaddCommand; -import com.redis.riot.cli.redis.TsAddCommand; -import com.redis.riot.cli.redis.XaddCommand; -import com.redis.riot.cli.redis.ZaddCommand; -import com.redis.riot.core.AbstractMapImport; -import com.redis.riot.core.ImportProcessorOptions; -import com.redis.spring.batch.operation.Operation; +import com.redis.riot.core.AbstractImport; import picocli.CommandLine.ArgGroup; -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -@Command(subcommands = { ExpireCommand.class, DelCommand.class, GeoaddCommand.class, HsetCommand.class, - LpushCommand.class, RpushCommand.class, SaddCommand.class, SetCommand.class, XaddCommand.class, - ZaddCommand.class, SugaddCommand.class, JsonSetCommand.class, - TsAddCommand.class }, subcommandsRepeatable = true, synopsisSubcommandLabel = "[REDIS COMMAND...]", commandListHeading = "Redis commands:%n") public abstract class AbstractImportCommand extends AbstractRiotCommand { - @Option(arity = "1..*", names = "--proc", description = "SpEL expressions in the form field1=\"exp\" field2=\"exp\"...", paramLabel = "") - private Map processorExpressions; + @ArgGroup(exclusive = false, heading = "Redis writer options%n") + private ImportArgs importArgs = new ImportArgs(); - @Option(names = "--filter", description = "Discard records using a SpEL expression.", paramLabel = "") - private Expression filter; + @Override + protected AbstractImport callable() { + AbstractImport callable = importCallable(); + callable.setRedisClientOptions(importArgs.getRedisClientArgs().redisClientOptions()); + callable.setWriterOptions(importArgs.getRedisWriterArgs().writerOptions()); + return callable; + } - @ArgGroup(exclusive = false) - private EvaluationContextArgs evaluationContextArgs = new EvaluationContextArgs(); + protected abstract AbstractImport importCallable(); - /** - * Initialized manually during command parsing - */ - private List commands = new ArrayList<>(); + public static class ImportArgs { - public List getCommands() { - return commands; - } + @ArgGroup(exclusive = false) + private RedisWriterArgs redisWriterArgs = new RedisWriterArgs(); - public void setCommands(List commands) { - this.commands = commands; - } + @ArgGroup(exclusive = false) + private RedisClientArgs redisClientArgs = new RedisClientArgs(); - protected List, Object>> operations() { - return commands.stream().map(RedisCommand::operation).collect(Collectors.toList()); - } + public RedisWriterArgs getRedisWriterArgs() { + return redisWriterArgs; + } - @Override - protected AbstractMapImport runnable() { - AbstractMapImport runnable = importRunnable(); - runnable.setOperations(operations()); - runnable.setEvaluationContextOptions(evaluationContextArgs.evaluationContextOptions()); - runnable.setProcessorOptions(processorOptions()); - return runnable; - } + public void setRedisWriterArgs(RedisWriterArgs args) { + this.redisWriterArgs = args; + } + + public RedisClientArgs getRedisClientArgs() { + return redisClientArgs; + } + + public void setRedisClientArgs(RedisClientArgs args) { + this.redisClientArgs = args; + } - private ImportProcessorOptions processorOptions() { - ImportProcessorOptions options = new ImportProcessorOptions(); - options.setProcessorExpressions(processorExpressions); - options.setFilterExpression(filter); - return options; } - protected abstract AbstractMapImport importRunnable(); + public ImportArgs getImportArgs() { + return importArgs; + } - @Override - protected String taskName(String stepName) { - return "Importing"; + public void setImportArgs(ImportArgs args) { + this.importArgs = args; } } diff --git a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMainCommand.java b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMainCommand.java index 97cbd4fc7..8e4b2e72d 100644 --- a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMainCommand.java +++ b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMainCommand.java @@ -1,6 +1,7 @@ package com.redis.riot.cli; import java.io.PrintWriter; +import java.util.Map; import org.springframework.expression.Expression; @@ -11,24 +12,38 @@ import picocli.AutoComplete.GenerateCompletion; import picocli.CommandLine; -import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.IExecutionStrategy; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; import picocli.CommandLine.ParseResult; import picocli.CommandLine.RunFirst; import picocli.CommandLine.RunLast; +import picocli.CommandLine.Spec; -@Command(subcommands = { DbImportCommand.class, DbExportCommand.class, FileDumpImportCommand.class, - FileImportCommand.class, FileDumpExportCommand.class, FakerImportCommand.class, GenerateCommand.class, - ReplicateCommand.class, PingCommand.class, GenerateCompletion.class }) -public abstract class AbstractMainCommand extends BaseCommand implements Runnable { +@Command(usageHelpAutoWidth = true, abbreviateSynopsis = true, subcommands = { DbImportCommand.class, + DbExportCommand.class, FileDumpImportCommand.class, FileImportCommand.class, FileExportCommand.class, + FakerImportCommand.class, GenerateCommand.class, ReplicateCommand.class, PingCommand.class, + GenerateCompletion.class }) +public abstract class AbstractMainCommand implements Runnable { + + static { + if (System.getenv().containsKey("RIOT_NO_COLOR")) { + System.setProperty("picocli.ansi", "false"); + } + } PrintWriter out; PrintWriter err; - @ArgGroup(exclusive = false) - private RedisArgs redisArgs = new RedisArgs(); + @Spec + CommandSpec spec; + + @Option(names = "-D", paramLabel = "", description = "Sets a System property.") + void setProperty(Map props) { + props.forEach(System::setProperty); + } @Override public void run() { @@ -69,13 +84,13 @@ private static int execute(CommandLine commandLine, String[] args, IExecutionStr private static int executionStrategy(ParseResult parseResult) { for (ParseResult subcommand : parseResult.subcommands()) { Object command = subcommand.commandSpec().userObject(); - if (AbstractImportCommand.class.isAssignableFrom(command.getClass())) { - AbstractImportCommand importCommand = (AbstractImportCommand) command; + if (AbstractMapImportCommand.class.isAssignableFrom(command.getClass())) { + AbstractMapImportCommand importCommand = (AbstractMapImportCommand) command; for (ParseResult redisCommand : subcommand.subcommands()) { if (redisCommand.isUsageHelpRequested()) { return new RunLast().execute(redisCommand); } - importCommand.getCommands().add((RedisCommand) redisCommand.commandSpec().userObject()); + importCommand.getCommands().add((RedisOperationCommand) redisCommand.commandSpec().userObject()); } return new RunFirst().execute(subcommand); } @@ -83,12 +98,4 @@ private static int executionStrategy(ParseResult parseResult) { return new RunLast().execute(parseResult); // default execution strategy } - public RedisArgs getRedisArgs() { - return redisArgs; - } - - public void setRedisArgs(RedisArgs redisArgs) { - this.redisArgs = redisArgs; - } - } diff --git a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMapImportCommand.java b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMapImportCommand.java new file mode 100644 index 000000000..ba2041ecd --- /dev/null +++ b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractMapImportCommand.java @@ -0,0 +1,86 @@ +package com.redis.riot.cli; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.expression.Expression; + +import com.redis.riot.cli.redis.DelCommand; +import com.redis.riot.cli.redis.ExpireCommand; +import com.redis.riot.cli.redis.GeoaddCommand; +import com.redis.riot.cli.redis.HsetCommand; +import com.redis.riot.cli.redis.JsonSetCommand; +import com.redis.riot.cli.redis.LpushCommand; +import com.redis.riot.cli.redis.RpushCommand; +import com.redis.riot.cli.redis.SaddCommand; +import com.redis.riot.cli.redis.SetCommand; +import com.redis.riot.cli.redis.SugaddCommand; +import com.redis.riot.cli.redis.TsAddCommand; +import com.redis.riot.cli.redis.XaddCommand; +import com.redis.riot.cli.redis.ZaddCommand; +import com.redis.riot.core.AbstractMapImport; +import com.redis.riot.core.ImportProcessorOptions; +import com.redis.spring.batch.operation.Operation; + +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command(subcommands = { ExpireCommand.class, DelCommand.class, GeoaddCommand.class, HsetCommand.class, + LpushCommand.class, RpushCommand.class, SaddCommand.class, SetCommand.class, XaddCommand.class, + ZaddCommand.class, SugaddCommand.class, JsonSetCommand.class, + TsAddCommand.class }, subcommandsRepeatable = true, synopsisSubcommandLabel = "[REDIS COMMAND...]", commandListHeading = "Redis commands:%n") +public abstract class AbstractMapImportCommand extends AbstractImportCommand { + + @Option(arity = "1..*", names = "--proc", description = "SpEL expressions in the form field1=\"exp\" field2=\"exp\"...", paramLabel = "") + private Map processorExpressions; + + @Option(names = "--filter", description = "Discard records using a SpEL expression.", paramLabel = "") + private Expression filter; + + @ArgGroup(exclusive = false) + private EvaluationContextArgs evaluationContextArgs = new EvaluationContextArgs(); + + /** + * Initialized manually during command parsing + */ + private List commands = new ArrayList<>(); + + public List getCommands() { + return commands; + } + + public void setCommands(List commands) { + this.commands = commands; + } + + protected List, Object>> operations() { + return commands.stream().map(RedisOperationCommand::operation).collect(Collectors.toList()); + } + + @Override + protected AbstractMapImport importCallable() { + AbstractMapImport callable = mapImportCallable(); + callable.setOperations(operations()); + callable.setEvaluationContextOptions(evaluationContextArgs.evaluationContextOptions()); + callable.setProcessorOptions(processorOptions()); + return callable; + } + + private ImportProcessorOptions processorOptions() { + ImportProcessorOptions options = new ImportProcessorOptions(); + options.setProcessorExpressions(processorExpressions); + options.setFilterExpression(filter); + return options; + } + + protected abstract AbstractMapImport mapImportCallable(); + + @Override + protected String taskName(String stepName) { + return "Importing"; + } + +} diff --git a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractRiotCommand.java b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractRiotCommand.java index 16de97633..1efe5ad39 100644 --- a/plugins/riot/src/main/java/com/redis/riot/cli/AbstractRiotCommand.java +++ b/plugins/riot/src/main/java/com/redis/riot/cli/AbstractRiotCommand.java @@ -1,6 +1,5 @@ package com.redis.riot.cli; -import java.time.Duration; import java.util.concurrent.Callable; import java.util.function.LongSupplier; import java.util.function.Supplier; @@ -15,94 +14,53 @@ import org.springframework.util.ClassUtils; import com.redis.riot.core.AbstractRiotCallable; -import com.redis.riot.core.AbstractRedisCallable; +import com.redis.riot.faker.FakerItemReader; +import com.redis.spring.batch.RedisItemReader; +import com.redis.spring.batch.RedisItemReader.ReaderMode; +import com.redis.spring.batch.gen.GeneratorItemReader; +import com.redis.spring.batch.reader.ScanSizeEstimator; import me.tongfei.progressbar.DelegatingProgressBarConsumer; import me.tongfei.progressbar.ProgressBarBuilder; -import me.tongfei.progressbar.ProgressBarStyle; +import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; -import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; -import picocli.CommandLine.ParentCommand; -@Command -abstract class AbstractRiotCommand extends BaseCommand implements Callable { +@Command(usageHelpAutoWidth = true, abbreviateSynopsis = true) +abstract class AbstractRiotCommand implements Callable { public enum ProgressStyle { BLOCK, BAR, ASCII, LOG, NONE } - @ParentCommand - protected AbstractMainCommand parent; + @Option(names = "--help", usageHelp = true, description = "Show this help message and exit.") + private boolean helpRequested; - @Mixin - LoggingMixin loggingMixin = new LoggingMixin(); + @ArgGroup(exclusive = false, heading = "Logging options%n") + private LoggingArgs loggingArgs = new LoggingArgs(); - @Option(names = "--sleep", description = "Duration in ms to sleep after each batch write (default: ${DEFAULT-VALUE}).", paramLabel = "") - long sleep; + @ArgGroup(exclusive = false, heading = "Job options%n") + private JobArgs jobArgs = new JobArgs(); - @Option(names = "--threads", description = "Number of concurrent threads to use for batch processing (default: ${DEFAULT-VALUE}).", paramLabel = "") - int threads = AbstractRiotCallable.DEFAULT_THREADS; - - @Option(names = { "-b", - "--batch" }, description = "Number of items in each batch (default: ${DEFAULT-VALUE}).", paramLabel = "") - int chunkSize = AbstractRiotCallable.DEFAULT_CHUNK_SIZE; - - @Option(names = "--dry-run", description = "Enable dummy writes.") - boolean dryRun; - - @Option(names = "--skip-limit", description = "Max number of failed items before considering the transfer has failed (default: ${DEFAULT-VALUE}).", paramLabel = "") - int skipLimit = AbstractRiotCallable.DEFAULT_SKIP_LIMIT; - - @Option(names = "--retry-limit", description = "Maximum number of times to try failed items. 0 and 1 both mean no retry. (default: ${DEFAULT-VALUE}).", paramLabel = "") - private int retryLimit = AbstractRiotCallable.DEFAULT_RETRY_LIMIT; - - @Option(names = "--progress", description = "Progress style: ${COMPLETION-CANDIDATES} (default: ${DEFAULT-VALUE}).", paramLabel = "