From 36be27e3cc7fe599102081d2c257528f8c14bcf6 Mon Sep 17 00:00:00 2001 From: Zixuan Liu Date: Wed, 20 Mar 2024 12:17:30 +0800 Subject: [PATCH] [improve][cli] PIP-343: Use picocli instead of jcommander in bin/pulsar Signed-off-by: Zixuan Liu --- pulsar-broker/pom.xml | 4 +- .../apache/pulsar/PulsarBrokerStarter.java | 135 ++++++----- .../pulsar/PulsarClusterMetadataSetup.java | 88 ++++---- .../pulsar/PulsarClusterMetadataTeardown.java | 32 +-- .../pulsar/PulsarInitialNamespaceSetup.java | 33 +-- .../org/apache/pulsar/PulsarStandalone.java | 39 ++-- .../pulsar/PulsarStandaloneStarter.java | 18 +- ...arTransactionCoordinatorMetadataSetup.java | 32 +-- .../apache/pulsar/PulsarVersionStarter.java | 22 +- .../pulsar/broker/tools/BrokerTool.java | 34 +-- .../broker/tools/GenerateDocsCommand.java | 61 ++--- .../broker/tools/LoadReportCommand.java | 78 +++---- .../pulsar/compaction/CompactorTool.java | 27 +-- .../utils/auth/tokens/TokensCliUtils.java | 211 +++++++++--------- .../pulsar/PulsarBrokerStarterTest.java | 109 ++++----- .../PulsarClusterMetadataSetupTest.java | 12 +- .../PulsarClusterMetadataTeardownTest.java | 6 +- .../PulsarInitialNamespaceSetupTest.java | 6 +- ...ansactionCoordinatorMetadataSetupTest.java | 6 +- .../pulsar/PulsarVersionStarterTest.java | 6 +- .../pulsar/broker/tools/BrokerToolTest.java | 8 +- .../pulsar/compaction/CompactorToolTest.java | 6 +- .../utils/auth/tokens/TokensCliUtilsTest.java | 8 +- pulsar-docs-tools/pom.xml | 4 +- .../docs/tools/BaseGenerateDocumentation.java | 50 ++--- .../pulsar/docs/tools/CmdGenerateDocs.java | 171 +++++++------- .../docs/tools/CmdGenerateDocsTest.java | 96 ++++---- .../worker/FunctionWorkerStarter.java | 25 ++- .../worker/FunctionWorkerStarterTest.java | 6 +- pulsar-io/docs/pom.xml | 4 +- pulsar-proxy/pom.xml | 4 +- .../proxy/server/ProxyServiceStarter.java | 34 +-- .../service/WebSocketServiceStarter.java | 25 ++- .../service/WebSocketServiceStarterTest.java | 6 +- 34 files changed, 691 insertions(+), 715 deletions(-) diff --git a/pulsar-broker/pom.xml b/pulsar-broker/pom.xml index db839467ed271..8264459c6d9ab 100644 --- a/pulsar-broker/pom.xml +++ b/pulsar-broker/pom.xml @@ -322,8 +322,8 @@ - com.beust - jcommander + info.picocli + picocli diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java index 1b24c806e62cb..bd5b5399b0091 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarBrokerStarter.java @@ -23,9 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.pulsar.common.configuration.PulsarConfigurationLoader.create; import static org.apache.pulsar.common.configuration.PulsarConfigurationLoader.isComplete; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.FileInputStream; @@ -34,10 +31,10 @@ import java.nio.file.Path; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import org.apache.bookkeeper.common.component.ComponentStarter; import org.apache.bookkeeper.common.component.LifecycleComponent; @@ -63,7 +60,13 @@ import org.apache.pulsar.functions.worker.service.WorkerServiceLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; +@Command(description = "broker", showDefaultValues = true, scope = ScopeType.INHERIT) public class PulsarBrokerStarter { private static ServiceConfiguration loadConfig(String configFile) throws Exception { @@ -76,31 +79,30 @@ private static ServiceConfiguration loadConfig(String configFile) throws Excepti } @VisibleForTesting - @Parameters(commandDescription = "Options") private static class StarterArguments { - @Parameter(names = {"-c", "--broker-conf"}, description = "Configuration file for Broker") + @Option(names = {"-c", "--broker-conf"}, description = "Configuration file for Broker") private String brokerConfigFile = "conf/broker.conf"; - @Parameter(names = {"-rb", "--run-bookie"}, description = "Run Bookie together with Broker") + @Option(names = {"-rb", "--run-bookie"}, description = "Run Bookie together with Broker") private boolean runBookie = false; - @Parameter(names = {"-ra", "--run-bookie-autorecovery"}, + @Option(names = {"-ra", "--run-bookie-autorecovery"}, description = "Run Bookie Autorecovery together with broker") private boolean runBookieAutoRecovery = false; - @Parameter(names = {"-bc", "--bookie-conf"}, description = "Configuration file for Bookie") + @Option(names = {"-bc", "--bookie-conf"}, description = "Configuration file for Bookie") private String bookieConfigFile = "conf/bookkeeper.conf"; - @Parameter(names = {"-rfw", "--run-functions-worker"}, description = "Run functions worker with Broker") + @Option(names = {"-rfw", "--run-functions-worker"}, description = "Run functions worker with Broker") private boolean runFunctionsWorker = false; - @Parameter(names = {"-fwc", "--functions-worker-conf"}, description = "Configuration file for Functions Worker") + @Option(names = {"-fwc", "--functions-worker-conf"}, description = "Configuration file for Functions Worker") private String fnWorkerConfigFile = "conf/functions_worker.yml"; - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } @@ -125,43 +127,46 @@ private static ServerConfiguration readBookieConfFile(String bookieConfigFile) t return bookieConf; } - private static boolean argsContains(String[] args, String arg) { - return Arrays.asList(args).contains(arg); - } - - private static class BrokerStarter { - private final ServiceConfiguration brokerConfig; - private final PulsarService pulsarService; - private final LifecycleComponent bookieServer; + protected static class BrokerStarter implements Callable { + private ServiceConfiguration brokerConfig; + private PulsarService pulsarService; + private LifecycleComponent bookieServer; private volatile CompletableFuture bookieStartFuture; - private final AutoRecoveryMain autoRecoveryMain; - private final StatsProvider bookieStatsProvider; - private final ServerConfiguration bookieConfig; - private final WorkerService functionsWorkerService; - private final WorkerConfig workerConfig; - - BrokerStarter(String[] args) throws Exception { - StarterArguments starterArguments = new StarterArguments(); - JCommander jcommander = new JCommander(starterArguments); - jcommander.setProgramName("PulsarBrokerStarter"); - - // parse args by JCommander - jcommander.parse(args); + private AutoRecoveryMain autoRecoveryMain; + private StatsProvider bookieStatsProvider; + private ServerConfiguration bookieConfig; + private WorkerService functionsWorkerService; + private WorkerConfig workerConfig; + + private CommandLine commander; + + @ArgGroup(exclusive = false) + private final StarterArguments starterArguments = new StarterArguments(); + + BrokerStarter() { + commander = new CommandLine(this); + } + + public int start(String[] args) { + return commander.execute(args); + } + + public Integer call() throws Exception { if (starterArguments.help) { - jcommander.usage(); - System.exit(0); + commander.usage(commander.getOut()); + return 0; } if (starterArguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("broker", starterArguments); + cmd.addCommand("broker", commander); cmd.run(null); - System.exit(0); + return 0; } // init broker config if (isBlank(starterArguments.brokerConfigFile)) { - jcommander.usage(); + commander.usage(commander.getOut()); throw new IllegalArgumentException("Need to specify a configuration file for broker"); } else { final String filepath = Path.of(starterArguments.brokerConfigFile) @@ -209,20 +214,16 @@ private static class BrokerStarter { }); // if no argument to run bookie in cmd line, read from pulsar config - if (!argsContains(args, "-rb") && !argsContains(args, "--run-bookie")) { - checkState(!starterArguments.runBookie, - "runBookie should be false if has no argument specified"); + if (!starterArguments.runBookie) { starterArguments.runBookie = brokerConfig.isEnableRunBookieTogether(); } - if (!argsContains(args, "-ra") && !argsContains(args, "--run-bookie-autorecovery")) { - checkState(!starterArguments.runBookieAutoRecovery, - "runBookieAutoRecovery should be false if has no argument specified"); + if (!starterArguments.runBookieAutoRecovery) { starterArguments.runBookieAutoRecovery = brokerConfig.isEnableRunBookieAutoRecoveryTogether(); } if ((starterArguments.runBookie || starterArguments.runBookieAutoRecovery) - && isBlank(starterArguments.bookieConfigFile)) { - jcommander.usage(); + && isBlank(starterArguments.bookieConfigFile)) { + commander.usage(commander.getOut()); throw new IllegalArgumentException("No configuration file for Bookie"); } @@ -257,9 +258,7 @@ && isBlank(starterArguments.bookieConfigFile)) { } else { autoRecoveryMain = null; } - } - public void start() throws Exception { if (bookieStatsProvider != null) { bookieStatsProvider.start(bookieConfig); log.info("started bookieStatsProvider."); @@ -275,15 +274,17 @@ public void start() throws Exception { pulsarService.start(); log.info("PulsarService started."); + return 0; } public void join() throws InterruptedException { - pulsarService.waitUntilClosed(); - - try { - pulsarService.close(); - } catch (PulsarServerException e) { - throw new RuntimeException(); + if (pulsarService != null) { + pulsarService.waitUntilClosed(); + try { + pulsarService.close(); + } catch (PulsarServerException e) { + throw new RuntimeException(); + } } if (bookieStartFuture != null) { @@ -301,8 +302,10 @@ public void shutdown() throws Exception { log.info("Shut down functions worker service successfully."); } - pulsarService.close(); - log.info("Shut down broker service successfully."); + if (pulsarService != null) { + pulsarService.close(); + log.info("Shut down broker service successfully."); + } if (bookieStatsProvider != null) { bookieStatsProvider.stop(); @@ -317,6 +320,11 @@ public void shutdown() throws Exception { log.info("Shut down autoRecoveryMain successfully."); } } + + @VisibleForTesting + CommandLine getCommander() { + return commander; + } } @@ -330,7 +338,7 @@ public static void main(String[] args) throws Exception { exception.printStackTrace(System.out); }); - BrokerStarter starter = new BrokerStarter(args); + BrokerStarter starter = new BrokerStarter(); Runtime.getRuntime().addShutdownHook( new Thread(() -> { try { @@ -344,16 +352,21 @@ public static void main(String[] args) throws Exception { ); PulsarByteBufAllocator.registerOOMListener(oomException -> { - if (starter.brokerConfig.isSkipBrokerShutdownOnOOM()) { + if (starter.brokerConfig != null && starter.brokerConfig.isSkipBrokerShutdownOnOOM()) { log.error("-- Received OOM exception: {}", oomException.getMessage(), oomException); } else { log.error("-- Shutting down - Received OOM exception: {}", oomException.getMessage(), oomException); - starter.pulsarService.shutdownNow(); + if (starter.pulsarService != null) { + starter.pulsarService.shutdownNow(); + } } }); try { - starter.start(); + int start = starter.start(args); + if (start != 0) { + System.exit(start); + } } catch (Throwable t) { log.error("Failed to start pulsar service.", t); ShutdownUtil.triggerImmediateForcefulShutdown(); diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java index 96ebadb1ff4aa..854a5179161a4 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataSetup.java @@ -19,8 +19,6 @@ package org.apache.pulsar; import static org.apache.pulsar.common.policies.data.PoliciesUtil.getBundles; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import java.io.IOException; import java.util.Collections; import java.util.Optional; @@ -58,6 +56,10 @@ import org.apache.pulsar.metadata.impl.ZKMetadataStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * Setup the metadata for a new Pulsar cluster. @@ -66,75 +68,76 @@ public class PulsarClusterMetadataSetup { private static final int DEFAULT_BUNDLE_NUMBER = 16; + @Command(name = "initialize-cluster-metadata", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(names = { "-c", "--cluster" }, description = "Cluster name", required = true) + @Option(names = {"-c", "--cluster"}, description = "Cluster name", required = true) private String cluster; - @Parameter(names = {"-bn", + @Option(names = {"-bn", "--default-namespace-bundle-number"}, description = "The bundle numbers for the default namespaces(public/default), default is 16", required = false) private int numberOfDefaultNamespaceBundles; - @Parameter(names = { "-uw", - "--web-service-url" }, description = "Web-service URL for new cluster", required = true) + @Option(names = {"-uw", + "--web-service-url"}, description = "Web-service URL for new cluster", required = true) private String clusterWebServiceUrl; - @Parameter(names = {"-tw", + @Option(names = {"-tw", "--web-service-url-tls"}, description = "Web-service URL for new cluster with TLS encryption", required = false) private String clusterWebServiceUrlTls; - @Parameter(names = { "-ub", - "--broker-service-url" }, description = "Broker-service URL for new cluster", required = false) + @Option(names = {"-ub", + "--broker-service-url"}, description = "Broker-service URL for new cluster", required = false) private String clusterBrokerServiceUrl; - @Parameter(names = {"-tb", + @Option(names = {"-tb", "--broker-service-url-tls"}, description = "Broker-service URL for new cluster with TLS encryption", required = false) private String clusterBrokerServiceUrlTls; - @Parameter(names = { "-zk", - "--zookeeper" }, description = "Local ZooKeeper quorum connection string", + @Option(names = {"-zk", + "--zookeeper"}, description = "Local ZooKeeper quorum connection string", required = false, hidden = true - ) + ) private String zookeeper; - @Parameter(names = { "-md", - "--metadata-store" }, description = "Metadata Store service url. eg: zk:my-zk:2181", required = false) + @Option(names = {"-md", + "--metadata-store"}, description = "Metadata Store service url. eg: zk:my-zk:2181", required = false) private String metadataStoreUrl; - @Parameter(names = { - "--zookeeper-session-timeout-ms" + @Option(names = { + "--zookeeper-session-timeout-ms" }, description = "Local zookeeper session timeout ms") private int zkSessionTimeoutMillis = 30000; - @Parameter(names = {"-gzk", + @Option(names = {"-gzk", "--global-zookeeper"}, description = "Global ZooKeeper quorum connection string", required = false, hidden = true) private String globalZookeeper; - @Parameter(names = {"-cs", + @Option(names = {"-cs", "--configuration-store"}, description = "Configuration Store connection string", hidden = true) private String configurationStore; - @Parameter(names = {"-cms", + @Option(names = {"-cms", "--configuration-metadata-store"}, description = "Configuration Metadata Store connection string", hidden = false) private String configurationMetadataStore; - @Parameter(names = { - "--initial-num-stream-storage-containers" + @Option(names = { + "--initial-num-stream-storage-containers" }, description = "Num storage containers of BookKeeper stream storage") private int numStreamStorageContainers = 16; - @Parameter(names = { + @Option(names = { "--initial-num-transaction-coordinators" }, description = "Num transaction coordinators will assigned in cluster") private int numTransactionCoordinators = 16; - @Parameter(names = { + @Option(names = { "--existing-bk-metadata-service-uri"}, description = "The metadata service URI of the existing BookKeeper cluster that you want to use") private String existingBkMetadataServiceUri; @@ -142,26 +145,26 @@ private static class Arguments { // Hide and marked as deprecated this flag because we use the new name '--existing-bk-metadata-service-uri' to // pass the service url. For compatibility of the command, we should keep both to avoid the exceptions. @Deprecated - @Parameter(names = { - "--bookkeeper-metadata-service-uri"}, - description = "The metadata service URI of the existing BookKeeper cluster that you want to use", - hidden = true) + @Option(names = { + "--bookkeeper-metadata-service-uri"}, + description = "The metadata service URI of the existing BookKeeper cluster that you want to use", + hidden = true) private String bookieMetadataServiceUri; - @Parameter(names = { "-pp", - "--proxy-protocol" }, + @Option(names = {"-pp", + "--proxy-protocol"}, description = "Proxy protocol to select type of routing at proxy. Possible Values: [SNI]", required = false) private ProxyProtocol clusterProxyProtocol; - @Parameter(names = { "-pu", - "--proxy-url" }, description = "Proxy-server URL to which to connect.", required = false) + @Option(names = {"-pu", + "--proxy-url"}, description = "Proxy-server URL to which to connect.", required = false) private String clusterProxyUrl; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } @@ -197,28 +200,27 @@ public static void main(String[] args) throws Exception { System.setProperty("bookkeeper.metadata.client.drivers", PulsarMetadataClientDriver.class.getName()); Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (arguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("initialize-cluster-metadata", arguments); + cmd.addCommand("initialize-cluster-metadata", commander); cmd.run(null); return; } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); throw e; } if (arguments.metadataStoreUrl == null && arguments.zookeeper == null) { System.err.println("Metadata store address argument is required (--metadata-store)"); - jcommander.usage(); + commander.usage(commander.getOut()); System.exit(1); } @@ -226,7 +228,7 @@ public static void main(String[] args) throws Exception { && arguments.globalZookeeper == null) { System.err.println( "Configuration metadata store address argument is required (--configuration-metadata-store)"); - jcommander.usage(); + commander.usage(commander.getOut()); System.exit(1); } @@ -234,7 +236,7 @@ public static void main(String[] args) throws Exception { || arguments.globalZookeeper != null)) { System.err.println("Configuration metadata store argument (--configuration-metadata-store) " + "supersedes the deprecated (--global-zookeeper and --configuration-store) argument"); - jcommander.usage(); + commander.usage(commander.getOut()); System.exit(1); } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java index 01a9eedcca357..a2984a352b9f8 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarClusterMetadataTeardown.java @@ -18,8 +18,6 @@ */ package org.apache.pulsar; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import com.google.protobuf.InvalidProtocolBufferException; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -41,35 +39,40 @@ import org.apache.pulsar.metadata.api.extended.MetadataStoreExtended; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * Teardown the metadata for a existed Pulsar cluster. */ public class PulsarClusterMetadataTeardown { + @Command(name = "delete-cluster-metadata", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(names = { "-zk", + @Option(names = { "-zk", "--zookeeper"}, description = "Local ZooKeeper quorum connection string", required = true) private String zookeeper; - @Parameter(names = { + @Option(names = { "--zookeeper-session-timeout-ms" }, description = "Local zookeeper session timeout ms") private int zkSessionTimeoutMillis = 30000; - @Parameter(names = { "-c", "-cluster", "--cluster" }, description = "Cluster name") + @Option(names = { "-c", "-cluster", "--cluster" }, description = "Cluster name") private String cluster; - @Parameter(names = { "-cs", "--configuration-store" }, description = "Configuration Store connection string") + @Option(names = { "-cs", "--configuration-store" }, description = "Configuration Store connection string") private String configurationStore; - @Parameter(names = { "--bookkeeper-metadata-service-uri" }, description = "Metadata service uri of BookKeeper") + @Option(names = { "--bookkeeper-metadata-service-uri" }, description = "Metadata service uri of BookKeeper") private String bkMetadataServiceUri; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = { "-h", "--help" }, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } @@ -78,22 +81,21 @@ private static class Arguments { public static void main(String[] args) throws Exception { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (arguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("delete-cluster-metadata", arguments); + cmd.addCommand("delete-cluster-metadata", commander); cmd.run(null); return; } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); throw e; } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java index 22b38e59676ec..891aa1aa42120 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarInitialNamespaceSetup.java @@ -18,67 +18,70 @@ */ package org.apache.pulsar; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import java.util.List; import org.apache.pulsar.broker.resources.PulsarResources; import org.apache.pulsar.common.naming.NamespaceName; import org.apache.pulsar.docs.tools.CmdGenerateDocs; import org.apache.pulsar.metadata.api.MetadataStore; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.ScopeType; /** * Setup the initial namespace of the cluster without startup the Pulsar broker. */ public class PulsarInitialNamespaceSetup { + @Command(name = "initialize-namespace", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(names = { "-c", "--cluster" }, description = "Cluster name", required = true) + @Option(names = { "-c", "--cluster" }, description = "Cluster name", required = true) private String cluster; - @Parameter(names = { "-cs", + @Option(names = { "-cs", "--configuration-store" }, description = "Configuration Store connection string", required = true) private String configurationStore; - @Parameter(names = { + @Option(names = { "--zookeeper-session-timeout-ms" }, description = "Local zookeeper session timeout ms") private int zkSessionTimeoutMillis = 30000; - @Parameter(description = "tenant/namespace", required = true) + @Parameters(description = "tenant/namespace", arity = "1") private List namespaces; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = { "-h", "--help" }, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } public static int doMain(String[] args) throws Exception { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return 0; } if (arguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("initialize-namespace", arguments); + cmd.addCommand("initialize-namespace", commander); cmd.run(null); return 0; } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); return 1; } if (arguments.configurationStore == null) { System.err.println("Configuration store address argument is required (--configuration-store)"); - jcommander.usage(); + commander.usage(commander.getOut()); return 1; } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java index ba136e7c91058..b785448cdacaf 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandalone.java @@ -20,7 +20,6 @@ import static org.apache.pulsar.common.naming.NamespaceName.SYSTEM_NAMESPACE; import static org.apache.pulsar.common.naming.SystemTopicNames.TRANSACTION_COORDINATOR_ASSIGN; -import com.beust.jcommander.Parameter; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; import io.netty.util.internal.PlatformDependent; @@ -52,8 +51,12 @@ import org.apache.pulsar.metadata.impl.ZKMetadataStore; import org.apache.pulsar.packages.management.storage.filesystem.FileSystemPackagesStorageProvider; import org.apache.pulsar.zookeeper.LocalBookkeeperEnsemble; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; @Slf4j +@Command(name = "standalone", showDefaultValues = true, scope = ScopeType.INHERIT) public class PulsarStandalone implements AutoCloseable { private static final String PULSAR_STANDALONE_USE_ZOOKEEPER = "PULSAR_STANDALONE_USE_ZOOKEEPER"; @@ -211,60 +214,60 @@ public boolean isHelp() { return help; } - @Parameter(names = { "-c", "--config" }, description = "Configuration file path") + @Option(names = { "-c", "--config" }, description = "Configuration file path") private String configFile; - @Parameter(names = { "--wipe-data" }, description = "Clean up previous ZK/BK data") + @Option(names = { "--wipe-data" }, description = "Clean up previous ZK/BK data") private boolean wipeData = false; - @Parameter(names = { "--num-bookies" }, description = "Number of local Bookies") + @Option(names = { "--num-bookies" }, description = "Number of local Bookies") private int numOfBk = 1; - @Parameter(names = { "--metadata-dir" }, + @Option(names = { "--metadata-dir" }, description = "Directory for storing metadata") private String metadataDir = "data/metadata"; - @Parameter(names = { "--metadata-url" }, + @Option(names = { "--metadata-url" }, description = "Metadata store url") private String metadataStoreUrl = ""; - @Parameter(names = {"--zookeeper-port"}, description = "Local zookeeper's port", + @Option(names = {"--zookeeper-port"}, description = "Local zookeeper's port", hidden = true) private int zkPort = 2181; - @Parameter(names = { "--bookkeeper-port" }, description = "Local bookies base port") + @Option(names = { "--bookkeeper-port" }, description = "Local bookies base port") private int bkPort = 3181; - @Parameter(names = { "--zookeeper-dir" }, + @Option(names = { "--zookeeper-dir" }, description = "Local zooKeeper's data directory", hidden = true) private String zkDir = "data/standalone/zookeeper"; - @Parameter(names = { "--bookkeeper-dir" }, description = "Local bookies base data directory") + @Option(names = { "--bookkeeper-dir" }, description = "Local bookies base data directory") private String bkDir = "data/standalone/bookkeeper"; - @Parameter(names = { "--no-broker" }, description = "Only start ZK and BK services, no broker") + @Option(names = { "--no-broker" }, description = "Only start ZK and BK services, no broker") private boolean noBroker = false; - @Parameter(names = { "--only-broker" }, description = "Only start Pulsar broker service (no ZK, BK)") + @Option(names = { "--only-broker" }, description = "Only start Pulsar broker service (no ZK, BK)") private boolean onlyBroker = false; - @Parameter(names = {"-nfw", "--no-functions-worker"}, description = "Run functions worker with Broker") + @Option(names = {"-nfw", "--no-functions-worker"}, description = "Run functions worker with Broker") private boolean noFunctionsWorker = false; - @Parameter(names = {"-fwc", "--functions-worker-conf"}, description = "Configuration file for Functions Worker") + @Option(names = {"-fwc", "--functions-worker-conf"}, description = "Configuration file for Functions Worker") private String fnWorkerConfigFile = "conf/functions_worker.yml"; - @Parameter(names = {"-nss", "--no-stream-storage"}, description = "Disable stream storage") + @Option(names = {"-nss", "--no-stream-storage"}, description = "Disable stream storage") private boolean noStreamStorage = false; - @Parameter(names = { "--stream-storage-port" }, description = "Local bookies stream storage port") + @Option(names = { "--stream-storage-port" }, description = "Local bookies stream storage port") private int streamStoragePort = 4181; - @Parameter(names = { "-a", "--advertised-address" }, description = "Standalone broker advertised address") + @Option(names = { "-a", "--advertised-address" }, description = "Standalone broker advertised address") private String advertisedAddress = null; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = { "-h", "--help" }, description = "Show this help message") private boolean help = false; private boolean usingNewDefaultsPIP117; diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java index e3a5e66d4b660..0ab731591da14 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarStandaloneStarter.java @@ -19,8 +19,6 @@ package org.apache.pulsar; import static org.apache.commons.lang3.StringUtils.isBlank; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import com.google.common.base.Strings; import java.io.FileInputStream; import java.util.Arrays; @@ -30,23 +28,25 @@ import org.apache.pulsar.broker.ServiceConfiguration; import org.apache.pulsar.common.configuration.PulsarConfigurationLoader; import org.apache.pulsar.docs.tools.CmdGenerateDocs; +import picocli.CommandLine; +import picocli.CommandLine.Option; @Slf4j public class PulsarStandaloneStarter extends PulsarStandalone { private static final String PULSAR_CONFIG_FILE = "pulsar.config.file"; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; public PulsarStandaloneStarter(String[] args) throws Exception { - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(this); + try { - jcommander.addObject(this); - jcommander.parse(args); + commander.parseArgs(args); if (this.isHelp()) { - jcommander.usage(); + commander.usage(commander.getOut()); exit(0); } if (Strings.isNullOrEmpty(this.getConfigFile())) { @@ -67,11 +67,11 @@ public PulsarStandaloneStarter(String[] args) throws Exception { if (this.isNoBroker() && this.isOnlyBroker()) { log.error("Only one option is allowed between '--no-broker' and '--only-broker'"); - jcommander.usage(); + commander.usage(commander.getOut()); return; } } catch (Exception e) { - jcommander.usage(); + commander.usage(commander.getOut()); log.error(e.getMessage()); exit(1); } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java index 6aedfe13a5b50..57b67b011913f 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetup.java @@ -18,13 +18,15 @@ */ package org.apache.pulsar; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import org.apache.pulsar.broker.resources.PulsarResources; import org.apache.pulsar.common.naming.NamespaceName; import org.apache.pulsar.common.naming.SystemTopicNames; import org.apache.pulsar.docs.tools.CmdGenerateDocs; import org.apache.pulsar.metadata.api.extended.MetadataStoreExtended; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * Set up the transaction coordinator metadata for a cluster, the setup will create pulsar/system namespace and create @@ -32,56 +34,56 @@ */ public class PulsarTransactionCoordinatorMetadataSetup { + @Command(name = "initialize-transaction-coordinator-metadata", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(names = { "-c", "--cluster" }, description = "Cluster name", required = true) + @Option(names = { "-c", "--cluster" }, description = "Cluster name", required = true) private String cluster; - @Parameter(names = { "-cs", + @Option(names = { "-cs", "--configuration-store" }, description = "Configuration Store connection string", required = true) private String configurationStore; - @Parameter(names = { + @Option(names = { "--zookeeper-session-timeout-ms" }, description = "Local zookeeper session timeout ms") private int zkSessionTimeoutMillis = 30000; - @Parameter(names = { + @Option(names = { "--initial-num-transaction-coordinators" }, description = "Num transaction coordinators will assigned in cluster") private int numTransactionCoordinators = 16; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = { "-h", "--help" }, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } public static void main(String[] args) throws Exception { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (arguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("initialize-transaction-coordinator-metadata", arguments); + cmd.addCommand("initialize-transaction-coordinator-metadata", commander); cmd.run(null); return; } } catch (Exception e) { - jcommander.usage(); + commander.usage(commander.getOut()); throw e; } if (arguments.configurationStore == null) { System.err.println("Configuration store address argument is required (--configuration-store)"); - jcommander.usage(); + commander.usage(commander.getOut()); System.exit(1); } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java index 85a6c4156dbe4..556b3ebfd84b6 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/PulsarVersionStarter.java @@ -18,41 +18,43 @@ */ package org.apache.pulsar; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import org.apache.pulsar.docs.tools.CmdGenerateDocs; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * Pulsar version entry point. */ public class PulsarVersionStarter { + @Command(name = "version", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } public static void main(String[] args) { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (arguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("version", arguments); + cmd.addCommand("version", commander); cmd.run(null); return; } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); return; } System.out.println("Current version of pulsar is: " + PulsarVersion.getVersion()); diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java index 2a479ce4b90c8..980c92fee8a5e 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/BrokerTool.java @@ -18,30 +18,32 @@ */ package org.apache.pulsar.broker.tools; -import org.apache.bookkeeper.tools.framework.Cli; -import org.apache.bookkeeper.tools.framework.CliFlags; -import org.apache.bookkeeper.tools.framework.CliSpec; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * broker-tool is used for operations on a specific broker. */ +@Command(name = "broker-tool", description = "broker-tool is used for operations on a specific broker", + showDefaultValues = true, scope = ScopeType.INHERIT) public class BrokerTool { - public static final String NAME = "broker-tool"; + @Option( + names = {"-h", "--help"}, + description = "Display help information", + usageHelp = true + ) + public boolean help = false; public static int run(String[] args) { - CliSpec.Builder specBuilder = CliSpec.newBuilder() - .withName(NAME) - .withUsage(NAME + " [flags] [commands]") - .withDescription(NAME + " is used for operations on a specific broker") - .withFlags(new CliFlags()) - .withConsole(System.out) - .addCommand(new LoadReportCommand()) - .addCommand(new GenerateDocsCommand()); - - CliSpec spec = specBuilder.build(); - - return Cli.runCli(spec, args); + BrokerTool brokerTool = new BrokerTool(); + CommandLine commander = new CommandLine(brokerTool); + GenerateDocsCommand generateDocsCommand = new GenerateDocsCommand(commander); + commander.addSubcommand(LoadReportCommand.class) + .addSubcommand(generateDocsCommand); + return commander.execute(args); } public static void main(String[] args) { diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java index b020b4bfd8bd1..b0ed54bc53fd0 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/GenerateDocsCommand.java @@ -18,63 +18,42 @@ */ package org.apache.pulsar.broker.tools; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import java.util.ArrayList; import java.util.List; -import org.apache.bookkeeper.tools.framework.Cli; -import org.apache.bookkeeper.tools.framework.CliCommand; -import org.apache.bookkeeper.tools.framework.CliFlags; -import org.apache.bookkeeper.tools.framework.CliSpec; +import java.util.concurrent.Callable; import org.apache.pulsar.docs.tools.CmdGenerateDocs; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; /** * The command to generate documents of broker-tool. */ -public class GenerateDocsCommand extends CliCommand { +@Command(name = "gen-doc", description = "Generate documents of broker-tool") +public class GenerateDocsCommand implements Callable { + @Option( + names = {"-n", "--command-names"}, + description = "List of command names", + arity = "0..1" + ) + private List commandNames = new ArrayList<>(); + private final CommandLine rootCmd; - private static final String NAME = "gen-doc"; - private static final String DESC = "Generate documents of broker-tool"; - - /** - * The CLI flags of gen docs command. - */ - protected static class GenDocFlags extends CliFlags { - @Parameter( - names = {"-n", "--command-names"}, - description = "List of command names" - ) - private List commandNames = new ArrayList<>(); - } - - public GenerateDocsCommand() { - super(CliSpec.newBuilder() - .withName(NAME) - .withDescription(DESC) - .withFlags(new GenDocFlags()) - .build()); + public GenerateDocsCommand(CommandLine rootCmd) { + this.rootCmd = rootCmd; } @Override - public Boolean apply(CliFlags globalFlags, String[] args) { - CliSpec newSpec = CliSpec.newBuilder(spec) - .withRunFunc(cmdFlags -> apply(cmdFlags)) - .build(); - return 0 == Cli.runCli(newSpec, args); - } - - private boolean apply(GenerateDocsCommand.GenDocFlags flags) { + public Integer call() throws Exception { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - JCommander commander = new JCommander(); - commander.addCommand("load-report", new LoadReportCommand.Flags()); - cmd.addCommand("broker-tool", commander); - if (flags.commandNames.isEmpty()) { + cmd.addCommand("broker-tool", rootCmd); + if (commandNames.isEmpty()) { cmd.run(null); } else { - ArrayList args = new ArrayList(flags.commandNames); + ArrayList args = new ArrayList(commandNames); args.add(0, "-n"); cmd.run(args.toArray(new String[0])); } - return true; + return 0; } } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java index 935e3a9f2fa1a..f1f4a917571be 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/tools/LoadReportCommand.java @@ -18,76 +18,48 @@ */ package org.apache.pulsar.broker.tools; -import com.beust.jcommander.Parameter; import java.util.Optional; +import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import org.apache.bookkeeper.tools.framework.Cli; -import org.apache.bookkeeper.tools.framework.CliCommand; -import org.apache.bookkeeper.tools.framework.CliFlags; -import org.apache.bookkeeper.tools.framework.CliSpec; import org.apache.commons.lang3.SystemUtils; import org.apache.pulsar.broker.loadbalance.BrokerHostUsage; import org.apache.pulsar.broker.loadbalance.impl.GenericBrokerHostUsageImpl; import org.apache.pulsar.broker.loadbalance.impl.LinuxBrokerHostUsageImpl; -import org.apache.pulsar.broker.tools.LoadReportCommand.Flags; import org.apache.pulsar.client.util.ExecutorProvider; import org.apache.pulsar.policies.data.loadbalancer.ResourceUsage; import org.apache.pulsar.policies.data.loadbalancer.SystemResourceUsage; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.Spec; /** * The command to collect the load report of a specific broker. */ -public class LoadReportCommand extends CliCommand { +@Command(name = "load-report", description = "Collect the load report of a specific broker") +public class LoadReportCommand implements Callable { - private static final String NAME = "load-report"; - private static final String DESC = "Collect the load report of a specific broker"; + @Option(names = {"-i", "--interval-ms"}, description = "Interval to collect load report, in milliseconds") + public int intervalMilliseconds = 100; - /** - * The CLI flags of load report command. - */ - public static class Flags extends CliFlags { - - @Parameter( - names = { - "-i", "--interval-ms" - }, - description = "Interval to collect load report, in milliseconds" - ) - public int intervalMilliseconds = 100; - - } - - public LoadReportCommand() { - super(CliSpec.newBuilder() - .withName(NAME) - .withDescription(DESC) - .withFlags(new Flags()) - .build()); - } + @Spec + CommandSpec spec; @Override - public Boolean apply(CliFlags globalFlags, String[] args) { - CliSpec newSpec = CliSpec.newBuilder(spec) - .withRunFunc(cmdFlags -> apply(cmdFlags)) - .build(); - return 0 == Cli.runCli(newSpec, args); - } - - private boolean apply(Flags flags) { - + public Integer call() throws Exception { boolean isLinux = SystemUtils.IS_OS_LINUX; - spec.console().println("OS ARCH: " + SystemUtils.OS_ARCH); - spec.console().println("OS NAME: " + SystemUtils.OS_NAME); - spec.console().println("OS VERSION: " + SystemUtils.OS_VERSION); - spec.console().println("Linux: " + isLinux); - spec.console().println("--------------------------------------"); - spec.console().println(); - spec.console().println("Load Report Interval : " + flags.intervalMilliseconds + " ms"); - spec.console().println(); - spec.console().println("--------------------------------------"); - spec.console().println(); + spec.commandLine().getOut().println("OS ARCH: " + SystemUtils.OS_ARCH); + spec.commandLine().getOut().println("OS NAME: " + SystemUtils.OS_NAME); + spec.commandLine().getOut().println("OS VERSION: " + SystemUtils.OS_VERSION); + spec.commandLine().getOut().println("Linux: " + isLinux); + spec.commandLine().getOut().println("--------------------------------------"); + spec.commandLine().getOut().println(); + spec.commandLine().getOut().println("Load Report Interval : " + intervalMilliseconds + " ms"); + spec.commandLine().getOut().println(); + spec.commandLine().getOut().println("--------------------------------------"); + spec.commandLine().getOut().println(); ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor( new ExecutorProvider.ExtendedThreadFactory("load-report")); @@ -105,7 +77,7 @@ private boolean apply(Flags flags) { hostUsage.calculateBrokerHostUsage(); try { - TimeUnit.MILLISECONDS.sleep(flags.intervalMilliseconds); + TimeUnit.MILLISECONDS.sleep(intervalMilliseconds); } catch (InterruptedException e) { } hostUsage.calculateBrokerHostUsage(); @@ -117,13 +89,13 @@ private boolean apply(Flags flags) { printResourceUsage("Bandwidth In", usage.bandwidthIn); printResourceUsage("Bandwidth Out", usage.bandwidthOut); - return true; + return 0; } finally { scheduler.shutdown(); } } private void printResourceUsage(String name, ResourceUsage usage) { - spec.console().println(name + " : usage = " + usage.usage + ", limit = " + usage.limit); + spec.commandLine().getOut().println(name + " : usage = " + usage.usage + ", limit = " + usage.limit); } } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java b/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java index 83ff790228108..f8cc95e6ac0ba 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/compaction/CompactorTool.java @@ -20,8 +20,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.channel.EventLoopGroup; import io.netty.util.concurrent.DefaultThreadFactory; @@ -48,20 +46,25 @@ import org.apache.pulsar.policies.data.loadbalancer.AdvertisedListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; +@Command(name = "compact-topic", showDefaultValues = true, scope = ScopeType.INHERIT) public class CompactorTool { private static class Arguments { - @Parameter(names = {"-c", "--broker-conf"}, description = "Configuration file for Broker") + @Option(names = {"-c", "--broker-conf"}, description = "Configuration file for Broker") private String brokerConfigFile = "conf/broker.conf"; - @Parameter(names = {"-t", "--topic"}, description = "Topic to compact", required = true) + @Option(names = {"-t", "--topic"}, description = "Topic to compact", required = true) private String topic; - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } @@ -107,13 +110,11 @@ public static PulsarClient createClient(ServiceConfiguration brokerConfig) throw public static void main(String[] args) throws Exception { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(arguments); - jcommander.setProgramName("PulsarTopicCompactor"); - - // parse args by JCommander - jcommander.parse(args); + CommandLine commander = new CommandLine(arguments); + commander.setCommandName("PulsarTopicCompactor"); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); System.exit(0); } @@ -126,7 +127,7 @@ public static void main(String[] args) throws Exception { // init broker config if (isBlank(arguments.brokerConfigFile)) { - jcommander.usage(); + commander.usage(commander.getOut()); throw new IllegalArgumentException("Need to specify a configuration file for broker"); } diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java b/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java index fa3a7bed8f641..4ae28b2c0bdb8 100644 --- a/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java +++ b/pulsar-broker/src/main/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtils.java @@ -18,11 +18,7 @@ */ package org.apache.pulsar.utils.auth.tokens; -import com.beust.jcommander.DefaultUsageFormatter; -import com.beust.jcommander.IUsageFormatter; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; +import com.google.common.annotations.VisibleForTesting; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; @@ -31,7 +27,6 @@ import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.security.Keys; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -40,34 +35,42 @@ import java.security.KeyPair; import java.util.Date; import java.util.Optional; +import java.util.concurrent.Callable; import javax.crypto.SecretKey; import lombok.Cleanup; import org.apache.pulsar.broker.authentication.utils.AuthTokenUtils; -import org.apache.pulsar.cli.converters.TimeUnitToSecondsConverter; +import org.apache.pulsar.cli.converters.picocli.TimeUnitToSecondsConverter; import org.apache.pulsar.docs.tools.CmdGenerateDocs; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.ScopeType; +@Command(name = "tokens", showDefaultValues = true, scope = ScopeType.INHERIT) public class TokensCliUtils { - public static class Arguments { - @Parameter(names = {"-h", "--help"}, description = "Show this help message") - private boolean help = false; - } + private final CommandLine commander; + + @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show this help message") + private boolean help; - @Parameters(commandDescription = "Create a new secret key") - public static class CommandCreateSecretKey { - @Parameter(names = {"-a", + @Command(description = "Create a new secret key") + public static class CommandCreateSecretKey implements Callable { + @Option(names = {"-a", "--signature-algorithm"}, description = "The signature algorithm for the new secret key.") SignatureAlgorithm algorithm = SignatureAlgorithm.HS256; - @Parameter(names = {"-o", + @Option(names = {"-o", "--output"}, description = "Write the secret key to a file instead of stdout") String outputFile; - @Parameter(names = { + @Option(names = { "-b", "--base64"}, description = "Encode the key in base64") boolean base64 = false; - public void run() throws IOException { + @Override + public Integer call() throws Exception { SecretKey secretKey = AuthTokenUtils.createSecretKey(algorithm); byte[] encoded = secretKey.getEncoded(); @@ -80,67 +83,73 @@ public void run() throws IOException { } else { System.out.write(encoded); } + + return 0; } } - @Parameters(commandDescription = "Create a new or pair of keys public/private") - public static class CommandCreateKeyPair { - @Parameter(names = {"-a", + @Command(description = "Create a new or pair of keys public/private") + public static class CommandCreateKeyPair implements Callable { + @Option(names = {"-a", "--signature-algorithm"}, description = "The signature algorithm for the new key pair.") SignatureAlgorithm algorithm = SignatureAlgorithm.RS256; - @Parameter(names = { + @Option(names = { "--output-private-key"}, description = "File where to write the private key", required = true) String privateKeyFile; - @Parameter(names = { + @Option(names = { "--output-public-key"}, description = "File where to write the public key", required = true) String publicKeyFile; - public void run() throws IOException { + @Override + public Integer call() throws Exception { KeyPair pair = Keys.keyPairFor(algorithm); Files.write(Paths.get(publicKeyFile), pair.getPublic().getEncoded()); Files.write(Paths.get(privateKeyFile), pair.getPrivate().getEncoded()); + + return 0; } } - @Parameters(commandDescription = "Create a new token") - public static class CommandCreateToken { - @Parameter(names = {"-a", + @Command(description = "Create a new token") + public static class CommandCreateToken implements Callable { + @Option(names = {"-a", "--signature-algorithm"}, description = "The signature algorithm for the new key pair.") SignatureAlgorithm algorithm = SignatureAlgorithm.RS256; - @Parameter(names = {"-s", + @Option(names = {"-s", "--subject"}, description = "Specify the 'subject' or 'principal' associate with this token", required = true) private String subject; - @Parameter(names = {"-e", + @Option(names = {"-e", "--expiry-time"}, description = "Relative expiry time for the token (eg: 1h, 3d, 10y)." + " (m=minutes) Default: no expiration", - converter = TimeUnitToSecondsConverter.class) + converter = TimeUnitToSecondsConverter.class) private Long expiryTime = null; - @Parameter(names = {"-sk", + @Option(names = {"-sk", "--secret-key"}, description = "Pass the secret key for signing the token. This can either be: data:, file:, etc..") private String secretKey; - @Parameter(names = {"-pk", + @Option(names = {"-pk", "--private-key"}, description = "Pass the private key for signing the token. This can either be: data:, file:, etc..") private String privateKey; - public void run() throws Exception { + @Override + public Integer call() throws Exception { if (secretKey == null && privateKey == null) { System.err.println( "Either --secret-key or --private-key needs to be passed for signing a token"); - System.exit(1); + return 1; } else if (secretKey != null && privateKey != null) { System.err.println( "Only one of --secret-key and --private-key needs to be passed for signing a token"); - System.exit(1); + return 1; } Key signingKey; @@ -159,27 +168,30 @@ public void run() throws Exception { String token = AuthTokenUtils.createToken(signingKey, subject, optExpiryTime); System.out.println(token); + + return 0; } } - @Parameters(commandDescription = "Show the content of token") - public static class CommandShowToken { + @Command(description = "Show the content of token") + public static class CommandShowToken implements Callable { - @Parameter(description = "The token string", arity = 1) - private java.util.List args; + @Parameters(description = "The token string", arity = "0..1") + private String args; - @Parameter(names = {"-i", + @Option(names = {"-i", "--stdin"}, description = "Read token from standard input") private Boolean stdin = false; - @Parameter(names = {"-f", + @Option(names = {"-f", "--token-file"}, description = "Read token from a file") private String tokenFile; - public void run() throws Exception { + @Override + public Integer call() throws Exception { String token; if (args != null) { - token = args.get(0); + token = args; } else if (stdin) { @Cleanup BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); @@ -192,59 +204,61 @@ public void run() throws Exception { System.err.println( "Token needs to be either passed as an argument or through `--stdin`," + " `--token-file` or by the `TOKEN` environment variable"); - System.exit(1); - return; + return 1; } String[] parts = token.split("\\."); System.out.println(new String(Decoders.BASE64URL.decode(parts[0]))); System.out.println("---"); System.out.println(new String(Decoders.BASE64URL.decode(parts[1]))); + + return 0; } } - @Parameters(commandDescription = "Validate a token against a key") - public static class CommandValidateToken { + @Command(description = "Validate a token against a key") + public static class CommandValidateToken implements Callable { - @Parameter(names = {"-a", + @Option(names = {"-a", "--signature-algorithm"}, description = "The signature algorithm for the key pair if using public key.") SignatureAlgorithm algorithm = SignatureAlgorithm.RS256; - @Parameter(description = "The token string", arity = 1) - private java.util.List args; + @Parameters(description = "The token string", arity = "0..1") + private String args; - @Parameter(names = {"-i", + @Option(names = {"-i", "--stdin"}, description = "Read token from standard input") private Boolean stdin = false; - @Parameter(names = {"-f", + @Option(names = {"-f", "--token-file"}, description = "Read token from a file") private String tokenFile; - @Parameter(names = {"-sk", + @Option(names = {"-sk", "--secret-key"}, description = "Pass the secret key for validating the token. This can either be: data:, file:, etc..") private String secretKey; - @Parameter(names = {"-pk", + @Option(names = {"-pk", "--public-key"}, description = "Pass the public key for validating the token. This can either be: data:, file:, etc..") private String publicKey; - public void run() throws Exception { + @Override + public Integer call() throws Exception { if (secretKey == null && publicKey == null) { System.err.println( "Either --secret-key or --public-key needs to be passed for signing a token"); - System.exit(1); + return 1; } else if (secretKey != null && publicKey != null) { System.err.println( "Only one of --secret-key and --public-key needs to be passed for signing a token"); - System.exit(1); + return 1; } String token; if (args != null) { - token = args.get(0); + token = args; } else if (stdin) { @Cleanup BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); @@ -257,8 +271,7 @@ public void run() throws Exception { System.err.println( "Token needs to be either passed as an argument or through `--stdin`," + " `--token-file` or by the `TOKEN` environment variable"); - System.exit(1); - return; + return 1; } Key validationKey; @@ -279,64 +292,46 @@ public void run() throws Exception { .parse(token); System.out.println(jwt.getBody()); + return 0; } } - public static void main(String[] args) throws Exception { - Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(arguments); - IUsageFormatter usageFormatter = new DefaultUsageFormatter(jcommander); - - CommandCreateSecretKey commandCreateSecretKey = new CommandCreateSecretKey(); - jcommander.addCommand("create-secret-key", commandCreateSecretKey); - - CommandCreateKeyPair commandCreateKeyPair = new CommandCreateKeyPair(); - jcommander.addCommand("create-key-pair", commandCreateKeyPair); - - CommandCreateToken commandCreateToken = new CommandCreateToken(); - jcommander.addCommand("create", commandCreateToken); - - CommandShowToken commandShowToken = new CommandShowToken(); - jcommander.addCommand("show", commandShowToken); + @Command + static class GenDoc implements Callable { - CommandValidateToken commandValidateToken = new CommandValidateToken(); - jcommander.addCommand("validate", commandValidateToken); + private final CommandLine rootCmd; - jcommander.addCommand("gen-doc", new Object()); - - try { - jcommander.parse(args); - - if (arguments.help || jcommander.getParsedCommand() == null) { - jcommander.usage(); - System.exit(1); - } - } catch (Exception e) { - System.err.println(e); - String chosenCommand = jcommander.getParsedCommand(); - usageFormatter.usage(chosenCommand); - System.exit(1); + public GenDoc(CommandLine rootCmd) { + this.rootCmd = rootCmd; } - String cmd = jcommander.getParsedCommand(); - - if (cmd.equals("create-secret-key")) { - commandCreateSecretKey.run(); - } else if (cmd.equals("create-key-pair")) { - commandCreateKeyPair.run(); - } else if (cmd.equals("create")) { - commandCreateToken.run(); - } else if (cmd.equals("show")) { - commandShowToken.run(); - } else if (cmd.equals("validate")) { - commandValidateToken.run(); - } else if (cmd.equals("gen-doc")) { + @Override + public Integer call() throws Exception { CmdGenerateDocs genDocCmd = new CmdGenerateDocs("pulsar"); - genDocCmd.addCommand("tokens", jcommander); + genDocCmd.addCommand("tokens", rootCmd); genDocCmd.run(null); - } else { - System.err.println("Invalid command: " + cmd); - System.exit(1); + + return 0; } } + + TokensCliUtils() { + commander = new CommandLine(this); + commander.addSubcommand("create-secret-key", CommandCreateSecretKey.class); + commander.addSubcommand("create-key-pair", CommandCreateKeyPair.class); + commander.addSubcommand("create", CommandCreateToken.class); + commander.addSubcommand("show", CommandShowToken.class); + commander.addSubcommand("validate", CommandValidateToken.class); + commander.addSubcommand("gen-doc", new GenDoc(commander)); + } + + @VisibleForTesting + int execute(String[] args) { + return commander.execute(args); + } + + public static void main(String[] args) throws Exception { + TokensCliUtils tokensCliUtils = new TokensCliUtils(); + System.exit(tokensCliUtils.execute(args)); + } } diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java index 1bc3bd26f1294..4c05a991b7ffe 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarBrokerStarterTest.java @@ -22,7 +22,6 @@ import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; -import com.beust.jcommander.Parameter; import com.google.common.collect.Sets; import java.io.ByteArrayOutputStream; import java.io.File; @@ -32,14 +31,16 @@ import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; -import java.lang.reflect.Constructor; +import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import lombok.Cleanup; +import org.apache.pulsar.PulsarBrokerStarter.BrokerStarter; import org.apache.pulsar.broker.ServiceConfiguration; -import org.apache.pulsar.docs.tools.CmdGenerateDocs; import org.testng.annotations.Test; +import picocli.CommandLine.Option; @Test(groups = "broker") public class PulsarBrokerStarterTest { @@ -282,12 +283,14 @@ public void testGlobalZooKeeperConfig() throws SecurityException, NoSuchMethodEx */ @Test public void testMainWithNoArgument() throws Exception { - try { - PulsarBrokerStarter.main(new String[0]); - fail("No argument to main should've raised FileNotFoundException for no broker config!"); - } catch (FileNotFoundException e) { - // code should reach here. - } + BrokerStarter brokerStarter = new BrokerStarter(); + @Cleanup + StringWriter err = new StringWriter(); + @Cleanup + PrintWriter printWriter = new PrintWriter(err); + brokerStarter.getCommander().setErr(printWriter); + assertEquals(brokerStarter.start(new String[0]), 1); + assertTrue(err.toString().contains("FileNotFoundException")); } /** @@ -296,16 +299,16 @@ public void testMainWithNoArgument() throws Exception { */ @Test public void testMainRunBookieAndAutoRecoveryNoConfig() throws Exception { - try { - File testConfigFile = createValidBrokerConfigFile(); - String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-ra", "-bc", ""}; - PulsarBrokerStarter.main(args); - fail("No Config file for bookie auto recovery should've raised IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // code should reach here. - e.printStackTrace(); - assertEquals(e.getMessage(), "No configuration file for Bookie"); - } + File testConfigFile = createValidBrokerConfigFile(); + String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-ra", "-bc", ""}; + BrokerStarter starter = new BrokerStarter(); + @Cleanup + StringWriter err = new StringWriter(); + @Cleanup + PrintWriter printWriter = new PrintWriter(err); + starter.getCommander().setErr(printWriter); + assertEquals(starter.start(args), 1); + assertTrue(err.toString().contains("No configuration file for Bookie")); } /** @@ -314,15 +317,16 @@ public void testMainRunBookieAndAutoRecoveryNoConfig() throws Exception { */ @Test public void testMainRunBookieRecoveryNoConfig() throws Exception { - try { - File testConfigFile = createValidBrokerConfigFile(); - String[] args = {"-c", testConfigFile.getAbsolutePath(), "-ra", "-bc", ""}; - PulsarBrokerStarter.main(args); - fail("No Config file for bookie auto recovery should've raised IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // code should reach here. - assertEquals(e.getMessage(), "No configuration file for Bookie"); - } + File testConfigFile = createValidBrokerConfigFile(); + String[] args = {"-c", testConfigFile.getAbsolutePath(), "-ra", "-bc", ""}; + BrokerStarter starter = new BrokerStarter(); + @Cleanup + StringWriter err = new StringWriter(); + @Cleanup + PrintWriter printWriter = new PrintWriter(err); + starter.getCommander().setErr(printWriter); + assertEquals(starter.start(args), 1); + assertTrue(err.toString().contains("No configuration file for Bookie")); } /** @@ -330,15 +334,16 @@ public void testMainRunBookieRecoveryNoConfig() throws Exception { */ @Test public void testMainRunBookieNoConfig() throws Exception { - try { - File testConfigFile = createValidBrokerConfigFile(); - String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-bc", ""}; - PulsarBrokerStarter.main(args); - fail("No Config file for bookie should've raised IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // code should reach here - assertEquals(e.getMessage(), "No configuration file for Bookie"); - } + File testConfigFile = createValidBrokerConfigFile(); + String[] args = {"-c", testConfigFile.getAbsolutePath(), "-rb", "-bc", ""}; + BrokerStarter starter = new BrokerStarter(); + @Cleanup + StringWriter err = new StringWriter(); + @Cleanup + PrintWriter printWriter = new PrintWriter(err); + starter.getCommander().setErr(printWriter); + assertEquals(starter.start(args), 1); + assertTrue(err.toString().contains("No configuration file for Bookie")); } /** @@ -346,14 +351,16 @@ public void testMainRunBookieNoConfig() throws Exception { */ @Test public void testMainEnableRunBookieThroughBrokerConfig() throws Exception { - try { - File testConfigFile = createValidBrokerConfigFile(); - String[] args = {"-c", testConfigFile.getAbsolutePath()}; - PulsarBrokerStarter.main(args); - fail("No argument to main should've raised IllegalArgumentException for no bookie config!"); - } catch (IllegalArgumentException e) { - // code should reach here. - } + File testConfigFile = createValidBrokerConfigFile(); + String[] args = {"-c", testConfigFile.getAbsolutePath()}; + BrokerStarter starter = new BrokerStarter(); + @Cleanup + StringWriter err = new StringWriter(); + @Cleanup + PrintWriter printWriter = new PrintWriter(err); + starter.getCommander().setErr(printWriter); + assertEquals(starter.start(args), 1); + assertTrue(err.toString().contains("IllegalArgumentException")); } @Test @@ -364,21 +371,15 @@ public void testMainGenerateDocs() throws Exception { System.setOut(new PrintStream(baoStream)); Class argumentsClass = Class.forName("org.apache.pulsar.PulsarBrokerStarter$StarterArguments"); - Constructor constructor = argumentsClass.getDeclaredConstructor(); - constructor.setAccessible(true); - Object obj = constructor.newInstance(); - - CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("broker", obj); - cmd.run(null); + PulsarBrokerStarter.main(new String[]{"-g"}); String message = baoStream.toString(); Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); String nameStr = Arrays.asList(names).toString(); nameStr = nameStr.substring(1, nameStr.length() - 1); diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java index 6196f66698869..710e040f8df1c 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataSetupTest.java @@ -19,13 +19,15 @@ package org.apache.pulsar; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; import org.testng.annotations.Test; +import picocli.CommandLine.Option; +@Slf4j public class PulsarClusterMetadataSetupTest { @Test public void testMainGenerateDocs() throws Exception { @@ -43,16 +45,16 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); - if (names.length == 0) { + if (names.length == 0 || fieldAnno.hidden()) { continue; } String nameStr = Arrays.asList(names).toString(); nameStr = nameStr.substring(1, nameStr.length() - 1); - assertTrue(message.indexOf(nameStr) > 0); + assertTrue(message.indexOf(nameStr) > 0, nameStr); } } } finally { diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java index f6a388dac76e3..95d12f378c55f 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarClusterMetadataTeardownTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class PulsarClusterMetadataTeardownTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length == 0) { continue; diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java index c1ad8c621c46d..0c6ba05b460e7 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarInitialNamespaceSetupTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class PulsarInitialNamespaceSetupTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length == 0) { continue; diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java index 70c7c7bd62ee9..6ff055385b2a4 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarTransactionCoordinatorMetadataSetupTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class PulsarTransactionCoordinatorMetadataSetupTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length == 0) { continue; diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java index 219e3b80cd308..b921c3d384315 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/PulsarVersionStarterTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class PulsarVersionStarterTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length == 0) { continue; diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java index ad2cf7784eb1f..063c041f2e0fe 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/tools/BrokerToolTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar.broker.tools; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; /** * Broker Tool Tests. @@ -47,12 +47,12 @@ public void testGenerateDocs() throws Exception { String message = baoStream.toString(); - Class argumentsClass = Class.forName("org.apache.pulsar.broker.tools.LoadReportCommand$Flags"); + Class argumentsClass = Class.forName("org.apache.pulsar.broker.tools.LoadReportCommand"); Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); String nameStr = Arrays.asList(names).toString(); nameStr = nameStr.substring(1, nameStr.length() - 1); diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java index fb8d6566d9a0d..72b8628cacaa8 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/compaction/CompactorToolTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Constructor; @@ -37,6 +36,7 @@ import org.apache.pulsar.client.api.PulsarClientException; import org.apache.pulsar.docs.tools.CmdGenerateDocs; import org.testng.annotations.Test; +import picocli.CommandLine.Option; /** * CompactorTool Tests. @@ -69,9 +69,9 @@ public void testGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); String nameStr = Arrays.asList(names).toString(); nameStr = nameStr.substring(1, nameStr.length() - 1); diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java index a488e4d958429..d5dc259438ea8 100644 --- a/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java +++ b/pulsar-broker/src/test/java/org/apache/pulsar/utils/auth/tokens/TokensCliUtilsTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar.utils.auth.tokens; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; /** * TokensCliUtils Tests. @@ -43,7 +43,7 @@ public void testGenerateDocs() throws Exception { ByteArrayOutputStream baoStream = new ByteArrayOutputStream(); System.setOut(new PrintStream(baoStream)); - TokensCliUtils.main(new String[]{"gen-doc"}); + new TokensCliUtils().execute(new String[]{"gen-doc"}); String message = baoStream.toString(); @@ -68,9 +68,9 @@ private void assertInnerClass(String className, String message) throws Exception Class argumentsClass = Class.forName(className); Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length < 1) { continue; diff --git a/pulsar-docs-tools/pom.xml b/pulsar-docs-tools/pom.xml index 40bddfde53276..e275d128fb01e 100644 --- a/pulsar-docs-tools/pom.xml +++ b/pulsar-docs-tools/pom.xml @@ -43,8 +43,8 @@ swagger-core - com.beust - jcommander + info.picocli + picocli diff --git a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java index db6178a7fda4d..ff474d98edc1a 100644 --- a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java +++ b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/BaseGenerateDocumentation.java @@ -18,8 +18,6 @@ */ package org.apache.pulsar.docs.tools; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; import java.lang.annotation.Annotation; @@ -28,6 +26,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.concurrent.Callable; import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.SneakyThrows; @@ -35,49 +34,44 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.commons.lang3.tuple.Pair; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; @Slf4j -public abstract class BaseGenerateDocumentation { +@Command(name = "gen-doc", showDefaultValues = true, scope = ScopeType.INHERIT) +public abstract class BaseGenerateDocumentation implements Callable { - JCommander jcommander; + CommandLine commander; - @Parameter(names = {"-c", "--class-names"}, description = + @Option(names = {"-c", "--class-names"}, description = "List of class names, generate documentation based on the annotations in the Class") private List classNames = new ArrayList<>(); - @Parameter(names = {"-h", "--help"}, help = true, description = "Show this help.") + @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show this help.") boolean help; public BaseGenerateDocumentation() { - jcommander = new JCommander(); - jcommander.setProgramName("pulsar-generateDocumentation"); - jcommander.addObject(this); + commander = new CommandLine(this); } - public boolean run(String[] args) throws Exception { - if (args.length == 0) { - jcommander.usage(); - return false; - } - - if (help) { - jcommander.usage(); - return true; - } - - try { - jcommander.parse(Arrays.copyOfRange(args, 0, args.length)); - } catch (Exception e) { - System.err.println(e.getMessage()); - jcommander.usage(); - return false; - } + @Override + public Integer call() throws Exception { if (classNames != null) { for (String className : classNames) { System.out.println(generateDocumentByClassName(className)); } } - return true; + return 0; + } + + public boolean run(String[] args) throws Exception { + if (args.length == 0) { + commander.usage(commander.getOut()); + return false; + } + return commander.execute(args) == 0; } protected abstract String generateDocumentByClassName(String className) throws Exception; diff --git a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java index 8f784c1eca1fa..a66da9fd6c650 100644 --- a/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java +++ b/pulsar-docs-tools/src/main/java/org/apache/pulsar/docs/tools/CmdGenerateDocs.java @@ -18,144 +18,155 @@ */ package org.apache.pulsar.docs.tools; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; -import com.beust.jcommander.ParameterDescription; -import com.beust.jcommander.Parameters; +import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.Map; +import java.util.concurrent.Callable; import lombok.Getter; import lombok.Setter; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.ArgSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; @Getter @Setter -@Parameters(commandDescription = "Generate documentation automatically.") -public class CmdGenerateDocs { +@Command(showDefaultValues = true, scope = ScopeType.INHERIT) +public class CmdGenerateDocs implements Callable { - @Parameter( + @Option( names = {"-h", "--help"}, - description = "Display help information" + description = "Display help information", + usageHelp = true ) public boolean help; - @Parameter( + @Option( names = {"-n", "--command-names"}, description = "List of command names" ) private List commandNames = new ArrayList<>(); private static final String name = "gen-doc"; - private final JCommander jcommander; + private final CommandLine commander; public CmdGenerateDocs(String cmdName) { - jcommander = new JCommander(this); - jcommander.setProgramName(cmdName); + commander = new CommandLine(this); + commander.setCommandName(cmdName); } public CmdGenerateDocs addCommand(String name, Object command) { - jcommander.addCommand(name, command); + commander.addSubcommand(name, command); return this; } public boolean run(String[] args) { - JCommander tmpCmd = new JCommander(this); - tmpCmd.setProgramName(jcommander.getProgramName() + " " + name); - try { - if (args == null) { - args = new String[]{}; - } - tmpCmd.parse(args); - } catch (Exception e) { - System.err.println(e.getMessage()); - System.err.println(); - tmpCmd.usage(); - return false; + if (args == null) { + args = new String[]{}; } - if (help) { - tmpCmd.usage(); - return true; + return commander.execute(args) == 0; + } + + private static String getCommandDescription(CommandLine commandLine) { + String[] description = commandLine.getCommandSpec().usageMessage().description(); + if (description != null && description.length != 0) { + return description[0]; } + return ""; + } - if (commandNames.size() == 0) { - for (Map.Entry cmd : jcommander.getCommands().entrySet()) { - if (cmd.getKey().equals(name)) { - continue; - } - System.out.println(generateDocument(cmd.getKey(), jcommander)); - } - } else { - for (String commandName : commandNames) { - if (commandName.equals(name)) { - continue; - } - if (!jcommander.getCommands().keySet().contains(commandName)) { - continue; - } - System.out.println(generateDocument(commandName, jcommander)); - } + private static String getArgDescription(ArgSpec argSpec) { + String[] description = argSpec.description(); + if (description != null && description.length != 0) { + return description[0]; } - return true; + return ""; } - private String generateDocument(String module, JCommander commander) { - JCommander cmd = commander.getCommands().get(module); + private String generateDocument(String module, CommandLine commander) { StringBuilder sb = new StringBuilder(); sb.append("# ").append(module).append("\n\n"); - String desc = commander.getUsageFormatter().getCommandDescription(module); + String desc = getCommandDescription(commander); if (null != desc && !desc.isEmpty()) { sb.append(desc).append("\n"); } sb.append("\n\n```shell\n") .append("$ "); - if (null != jcommander.getProgramName() && !jcommander.getProgramName().isEmpty()) { - sb.append(jcommander.getProgramName()).append(" "); - } - sb.append(module); - if (cmd.getObjects().size() > 0 - && cmd.getObjects().get(0).getClass().getName().equals("com.beust.jcommander.JCommander")) { - JCommander cmdObj = (JCommander) cmd.getObjects().get(0); + String commandName = commander.getCommandName(); + sb.append(this.commander.getCommandName() + " " + commandName); + if (!commander.getSubcommands().isEmpty()) { sb.append(" subcommand").append("\n```").append("\n\n"); - cmdObj.getCommands().forEach((subK, subV) -> { + commander.getSubcommands().forEach((subK, subV) -> { if (!subK.equals(name)) { sb.append("\n\n## ").append(subK).append("\n\n"); - String subDesc = cmdObj.getUsageFormatter().getCommandDescription(subK); + String subDesc = getCommandDescription(subV); if (null != subDesc && !subDesc.isEmpty()) { sb.append(subDesc).append("\n"); } sb.append("```shell\n$ "); - if (null != jcommander.getProgramName() && !jcommander.getProgramName().isEmpty()) { - sb.append(jcommander.getProgramName()).append(" "); - } + sb.append(this.commander.getCommandName()).append(" "); sb.append(module).append(" ").append(subK).append(" options").append("\n```\n\n"); - List options = cmdObj.getCommands().get(subK).getParameters(); - if (options.size() > 0) { + List argSpecs = subV.getCommandSpec().args(); + if (argSpecs.size() > 0) { sb.append("|Flag|Description|Default|\n"); sb.append("|---|---|---|\n"); } - options.forEach((option) -> - sb.append("| `").append(option.getNames()) - .append("` | ").append(option.getDescription().replace("\n", " ")) - .append("|").append(option.getDefault()).append("|\n") - ); + + argSpecs.forEach(option -> { + if (option.hidden() || !(option instanceof OptionSpec)) { + return; + } + sb.append("| `").append(String.join(", ", ((OptionSpec) option).names())) + .append("` | ").append(getArgDescription(option).replace("\n", " ")) + .append("|").append(option.defaultValueString()).append("|\n"); + }); } }); } else { sb.append(" options").append("\n```").append("\n\n"); sb.append("|Flag|Description|Default|\n"); sb.append("|---|---|---|\n"); - List options = cmd.getParameters(); - options.stream().sorted(Comparator.comparing(ParameterDescription::getLongestName)) - .forEach((option) -> - sb.append("| `") - .append(option.getNames()) - .append("` | ") - .append(option.getDescription().replace("\n", " ")) - .append("|") - .append(option.getDefault()).append("|\n") - ); + List argSpecs = commander.getCommandSpec().args(); + argSpecs.forEach(option -> { + if (option.hidden() || !(option instanceof OptionSpec)) { + return; + } + sb.append("| `") + .append(String.join(", ", ((OptionSpec) option).names())) + .append("` | ") + .append(getArgDescription(option).replace("\n", " ")) + .append("|") + .append(option.defaultValueString()).append("|\n"); + }); } return sb.toString(); } + + @Override + public Integer call() throws Exception { + if (commandNames.size() == 0) { + commander.getSubcommands().forEach((name, cmd) -> { + commander.getOut().println(generateDocument(name, cmd)); + }); + } else { + for (String commandName : commandNames) { + if (commandName.equals(name)) { + continue; + } + CommandLine cmd = commander.getSubcommands().get(commandName); + if (cmd == null) { + continue; + } + commander.getOut().println(generateDocument(commandName, cmd)); + } + } + return 0; + } + + @VisibleForTesting + CommandLine getCommander() { + return commander; + } } diff --git a/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java b/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java index 3f96ddaef591a..0f0f96f80ecb0 100644 --- a/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java +++ b/pulsar-docs-tools/src/test/java/org/apache/pulsar/docs/tools/CmdGenerateDocsTest.java @@ -19,79 +19,63 @@ package org.apache.pulsar.docs.tools; import static org.testng.Assert.assertEquals; -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import org.testng.annotations.Test; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; public class CmdGenerateDocsTest { - @Parameters(commandDescription = "Options") + @Command public class Arguments { - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-n", "--name"}, description = "Name") + @Option(names = {"-n", "--name"}, description = "Name") private String name; } @Test public void testHelp() { - PrintStream oldStream = System.out; - try { - ByteArrayOutputStream baoStream = new ByteArrayOutputStream(2048); - PrintStream cacheStream = new PrintStream(baoStream); - System.setOut(cacheStream); + CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); + cmd.addCommand("test", new Arguments()); + StringWriter stringWriter = new StringWriter(); + cmd.getCommander().setOut(new PrintWriter(stringWriter)); + cmd.run(new String[]{"-h"}); - CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("test", new Arguments()); - cmd.run(new String[]{"-h"}); - - String message = baoStream.toString(); - String rightMsg = "Usage: pulsar gen-doc [options]\n" - + " Options:\n" - + " -n, --command-names\n" - + " List of command names\n" - + " Default: []\n" - + " -h, --help\n" - + " Display help information\n" - + " Default: false\n" - + System.lineSeparator(); - assertEquals(rightMsg, message); - } finally { - System.setOut(oldStream); - } + String message = stringWriter.toString(); + String rightMsg = "Usage: pulsar [-h] [-n=]... [COMMAND]\n" + + " -h, --help Display help information\n" + + " -n, --command-names=\n" + + " List of command names\n" + + " Default: []\n" + + "Commands:\n" + + " test\n"; + assertEquals(message, rightMsg); } @Test public void testGenerateDocs() { - PrintStream oldStream = System.out; - try { - ByteArrayOutputStream baoStream = new ByteArrayOutputStream(2048); - PrintStream cacheStream = new PrintStream(baoStream); - System.setOut(cacheStream); - - CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("test", new Arguments()); - cmd.run(null); + CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); + cmd.addCommand("test", new Arguments()); + StringWriter stringWriter = new StringWriter(); + cmd.getCommander().setOut(new PrintWriter(stringWriter)); + cmd.run(null); - String message = baoStream.toString(); - String rightMsg = "# test\n\n" - + "Options\n\n" - + "\n" - + "```shell\n" - + "$ pulsar test options\n" - + "```\n" - + "\n" - + "|Flag|Description|Default|\n" - + "|---|---|---|\n" - + "| `-h, --help` | Show this help message|false|\n" - + "| `-n, --name` | Name|null|\n" - + System.lineSeparator(); - assertEquals(rightMsg, message); - } finally { - System.setOut(oldStream); - } + String message = stringWriter.toString(); + String rightMsg = "# test\n\n" + + "\n" + + "\n" + + "```shell\n" + + "$ pulsar test options\n" + + "```\n" + + "\n" + + "|Flag|Description|Default|\n" + + "|---|---|---|\n" + + "| `-h, --help` | Show this help message|false|\n" + + "| `-n, --name` | Name|null|\n" + + System.lineSeparator(); + assertEquals(message, rightMsg); } } diff --git a/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java b/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java index 679ce1db70d97..c5fb552d9cc80 100644 --- a/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java +++ b/pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/FunctionWorkerStarter.java @@ -19,11 +19,13 @@ package org.apache.pulsar.functions.worker; import static org.apache.commons.lang3.StringUtils.isBlank; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import lombok.extern.slf4j.Slf4j; import org.apache.pulsar.common.util.ShutdownUtil; import org.apache.pulsar.docs.tools.CmdGenerateDocs; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * A starter to start function worker. @@ -31,35 +33,36 @@ @Slf4j public class FunctionWorkerStarter { + @Command(name = "functions-worker", showDefaultValues = true, scope = ScopeType.INHERIT) private static class WorkerArguments { - @Parameter( + @Option( names = { "-c", "--conf" }, description = "Configuration File for Function Worker") private String configFile; - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } + public static void main(String[] args) throws Exception { WorkerArguments workerArguments = new WorkerArguments(); - JCommander commander = new JCommander(workerArguments); - commander.setProgramName("FunctionWorkerStarter"); + CommandLine commander = new CommandLine(workerArguments); + commander.setCommandName("FunctionWorkerStarter"); - // parse args by commander - commander.parse(args); + commander.parseArgs(args); if (workerArguments.help) { - commander.usage(); + commander.usage(commander.getOut()); return; } if (workerArguments.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("functions-worker", workerArguments); + cmd.addCommand("functions-worker", commander); cmd.run(null); return; } diff --git a/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java b/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java index 51bcd974f509a..6fa0bec1b362d 100644 --- a/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java +++ b/pulsar-functions/worker/src/test/java/org/apache/pulsar/functions/worker/FunctionWorkerStarterTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar.functions.worker; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class FunctionWorkerStarterTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); String nameStr = Arrays.asList(names).toString(); nameStr = nameStr.substring(1, nameStr.length() - 1); diff --git a/pulsar-io/docs/pom.xml b/pulsar-io/docs/pom.xml index adbc2f4efad39..0fd774aaafeb5 100644 --- a/pulsar-io/docs/pom.xml +++ b/pulsar-io/docs/pom.xml @@ -42,8 +42,8 @@ reflections - com.beust - jcommander + info.picocli + picocli diff --git a/pulsar-proxy/pom.xml b/pulsar-proxy/pom.xml index 55dfd11e40e93..64ca301facf4d 100644 --- a/pulsar-proxy/pom.xml +++ b/pulsar-proxy/pom.xml @@ -184,8 +184,8 @@ - com.beust - jcommander + info.picocli + picocli diff --git a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java index 1a98601f2a95d..72d54601995f1 100644 --- a/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java +++ b/pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyServiceStarter.java @@ -23,8 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.pulsar.common.stats.JvmMetrics.getJvmDirectMemoryUsed; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import com.google.common.annotations.VisibleForTesting; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; @@ -61,40 +59,45 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; /** * Starts an instance of the Pulsar ProxyService. */ +@Command(name = "proxy", showDefaultValues = true, scope = ScopeType.INHERIT) public class ProxyServiceStarter { - @Parameter(names = { "-c", "--config" }, description = "Configuration file path", required = true) + @Option(names = { "-c", "--config" }, description = "Configuration file path", required = true) private String configFile; @Deprecated - @Parameter(names = { "-zk", "--zookeeper-servers" }, + @Option(names = { "-zk", "--zookeeper-servers" }, description = "Local zookeeper connection string, please use --metadata-store instead") private String zookeeperServers = ""; - @Parameter(names = { "-md", "--metadata-store" }, description = "Metadata Store service url. eg: zk:my-zk:2181") + @Option(names = { "-md", "--metadata-store" }, description = "Metadata Store service url. eg: zk:my-zk:2181") private String metadataStoreUrl = ""; @Deprecated - @Parameter(names = { "-gzk", "--global-zookeeper-servers" }, + @Option(names = { "-gzk", "--global-zookeeper-servers" }, description = "Global zookeeper connection string, please use --configuration-metadata-store instead") private String globalZookeeperServers = ""; @Deprecated - @Parameter(names = { "-cs", "--configuration-store-servers" }, + @Option(names = { "-cs", "--configuration-store-servers" }, description = "Configuration store connection string, " + "please use --configuration-metadata-store instead") private String configurationStoreServers = ""; - @Parameter(names = { "-cms", "--configuration-metadata-store" }, + @Option(names = { "-cms", "--configuration-metadata-store" }, description = "The metadata store URL for the configuration data") private String configurationMetadataStoreUrl = ""; - @Parameter(names = { "-h", "--help" }, description = "Show this help message") + @Option(names = { "-h", "--help" }, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; private ProxyConfiguration config; @@ -116,23 +119,22 @@ public ProxyServiceStarter(String[] args) throws Exception { exception.printStackTrace(System.out); }); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(this); try { - jcommander.addObject(this); - jcommander.parse(args); + commander.parseArgs(args); if (help || isBlank(configFile)) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (this.generateDocs) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("proxy", this); + cmd.addCommand("proxy", commander); cmd.run(null); System.exit(0); } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); System.exit(1); } diff --git a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java index fd38208323c49..ed1a99b813331 100644 --- a/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java +++ b/pulsar-websocket/src/main/java/org/apache/pulsar/websocket/service/WebSocketServiceStarter.java @@ -22,8 +22,6 @@ import static org.apache.pulsar.websocket.admin.WebSocketWebResource.ADMIN_PATH_V1; import static org.apache.pulsar.websocket.admin.WebSocketWebResource.ADMIN_PATH_V2; import static org.apache.pulsar.websocket.admin.WebSocketWebResource.ATTRIBUTE_PROXY_SERVICE_NAME; -import com.beust.jcommander.JCommander; -import com.beust.jcommander.Parameter; import org.apache.pulsar.common.configuration.PulsarConfigurationLoader; import org.apache.pulsar.common.configuration.VipStatus; import org.apache.pulsar.common.util.ShutdownUtil; @@ -37,37 +35,42 @@ import org.apache.pulsar.websocket.admin.v2.WebSocketProxyStatsV2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.ScopeType; public class WebSocketServiceStarter { + @Command(name = "websocket", showDefaultValues = true, scope = ScopeType.INHERIT) private static class Arguments { - @Parameter(description = "config file") + @Parameters(description = "config file", arity = "0..1") private String configFile = ""; - @Parameter(names = {"-h", "--help"}, description = "Show this help message") + @Option(names = {"-h", "--help"}, description = "Show this help message") private boolean help = false; - @Parameter(names = {"-g", "--generate-docs"}, description = "Generate docs") + @Option(names = {"-g", "--generate-docs"}, description = "Generate docs") private boolean generateDocs = false; } public static void main(String[] args) throws Exception { Arguments arguments = new Arguments(); - JCommander jcommander = new JCommander(); + CommandLine commander = new CommandLine(arguments); try { - jcommander.addObject(arguments); - jcommander.parse(args); + commander.parseArgs(args); if (arguments.help) { - jcommander.usage(); + commander.usage(commander.getOut()); return; } if (arguments.generateDocs && arguments.configFile != null) { CmdGenerateDocs cmd = new CmdGenerateDocs("pulsar"); - cmd.addCommand("websocket", arguments); + cmd.addCommand("websocket", commander); cmd.run(null); return; } } catch (Exception e) { - jcommander.usage(); + commander.getErr().println(e); return; } diff --git a/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java b/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java index c898a07e22800..f9a190cf8662d 100644 --- a/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java +++ b/pulsar-websocket/src/test/java/org/apache/pulsar/websocket/service/WebSocketServiceStarterTest.java @@ -19,12 +19,12 @@ package org.apache.pulsar.websocket.service; import static org.testng.Assert.assertTrue; -import com.beust.jcommander.Parameter; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Field; import java.util.Arrays; import org.testng.annotations.Test; +import picocli.CommandLine.Option; public class WebSocketServiceStarterTest { @Test @@ -43,9 +43,9 @@ public void testMainGenerateDocs() throws Exception { Field[] fields = argumentsClass.getDeclaredFields(); for (Field field : fields) { - boolean fieldHasAnno = field.isAnnotationPresent(Parameter.class); + boolean fieldHasAnno = field.isAnnotationPresent(Option.class); if (fieldHasAnno) { - Parameter fieldAnno = field.getAnnotation(Parameter.class); + Option fieldAnno = field.getAnnotation(Option.class); String[] names = fieldAnno.names(); if (names.length == 0) { continue;