blob: cc6b8f4cf4a49fc2c52773cebdbac2de37a45f4a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.toolkit.cli;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.client.NiFiRegistryClient;
import org.apache.nifi.toolkit.cli.api.ClientFactory;
import org.apache.nifi.toolkit.cli.api.Command;
import org.apache.nifi.toolkit.cli.api.CommandGroup;
import org.apache.nifi.toolkit.cli.api.Context;
import org.apache.nifi.toolkit.cli.api.Session;
import org.apache.nifi.toolkit.cli.impl.client.NiFiClientFactory;
import org.apache.nifi.toolkit.cli.impl.client.NiFiRegistryClientFactory;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
import org.apache.nifi.toolkit.cli.impl.command.CommandFactory;
import org.apache.nifi.toolkit.cli.impl.command.CommandProcessor;
import org.apache.nifi.toolkit.cli.impl.context.StandardContext;
import org.apache.nifi.toolkit.cli.impl.session.InMemorySession;
import org.apache.nifi.toolkit.cli.impl.session.PersistentSession;
import org.jline.reader.Completer;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.ParsedLine;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Map;
/**
* Main entry point for the CLI.
*
* If the main method is executed with no arguments, then the interactive shell is launched.
*
* If the main method is executed with arguments, then a single command will be executed, and the process will exit.
*/
public class CLIMain {
public static final String SHELL_NAME = "nifi-registry";
public static final String PROMPT = "#> ";
public static final String BANNER_FILE = "nifi-banner.txt";
public static final String SESSION_PERSISTENCE_FILE = ".nifi-cli.config";
public static void main(String[] args) throws IOException {
if (args == null || args.length == 0) {
runInteractiveCLI();
} else {
// in standalone mode we want to make sure the process exits with the correct status
try {
final int returnCode = runSingleCommand(args);
System.exit(returnCode);
} catch (Exception e) {
// shouldn't really get here, but just in case
e.printStackTrace();
System.exit(-1);
}
}
}
/**
* Runs the interactive CLI.
*
* @throws IOException if an error occurs
*/
private static void runInteractiveCLI() throws IOException {
//Logger.getLogger("org.jline").setLevel(Level.FINE);
try (final Terminal terminal = TerminalBuilder.builder()
.name(SHELL_NAME)
.system(true)
.nativeSignals(true)
.signalHandler(Terminal.SignalHandler.SIG_IGN)
.build();
final PrintStream output = new PrintStream(terminal.output(), true)) {
printHeader(BANNER_FILE, output);
final Context context = createContext(output, true);
final Map<String,Command> topLevelCommands = CommandFactory.createTopLevelCommands(context);
final Map<String,CommandGroup> commandGroups = CommandFactory.createCommandGroups(context);
final CommandProcessor commandProcessor = new CommandProcessor(topLevelCommands, commandGroups, context);
final Completer completer = new CLICompleter(topLevelCommands.values(), commandGroups.values());
final LineReader reader = LineReaderBuilder.builder()
.appName(SHELL_NAME)
.terminal(terminal)
.completer(completer)
.build();
reader.setOpt(LineReader.Option.AUTO_FRESH_LINE);
reader.unsetOpt(LineReader.Option.INSERT_TAB);
while (true) {
try {
final String line = reader.readLine(PROMPT);
if (StringUtils.isBlank(line)) {
continue;
}
final ParsedLine parsedLine = reader.getParsedLine();
final String[] parsedArgs = parsedLine.words().toArray(new String[parsedLine.words().size()]);
commandProcessor.process(parsedArgs);
} catch (UserInterruptException e) {
// Ignore
} catch (EndOfFileException e) {
return;
}
}
}
}
/**
* Handles running a single command and exiting, non-interactive mode.
*
* @param args the args passed in from the command line
*/
public static int runSingleCommand(final String[] args) {
final Context context = createContext(System.out, false);
final Map<String,Command> topLevelCommands = CommandFactory.createTopLevelCommands(context);
final Map<String,CommandGroup> commandGroups = CommandFactory.createCommandGroups(context);
final CommandProcessor commandProcessor = new CommandProcessor(topLevelCommands, commandGroups, context);
return commandProcessor.process(args);
}
private static Context createContext(final PrintStream output, final boolean isInteractive) {
Session session;
final String userHomeValue = System.getProperty("user.home");
final File userHome = Paths.get(userHomeValue).toFile();
if (!userHome.exists() || !userHome.canRead() || !userHome.canWrite()) {
session = new InMemorySession();
if (isInteractive) {
output.println();
output.println("Unable to create session from " + userHomeValue + ", falling back to in-memory session");
output.println();
}
} else {
final InMemorySession inMemorySession = new InMemorySession();
final File sessionState = new File(userHome.getAbsolutePath(), SESSION_PERSISTENCE_FILE);
try {
if (!sessionState.exists()) {
sessionState.createNewFile();
}
final PersistentSession persistentSession = new PersistentSession(sessionState, inMemorySession);
persistentSession.loadSession();
session = persistentSession;
if (isInteractive) {
output.println();
output.println("Session loaded from " + sessionState.getAbsolutePath());
output.println();
}
} catch (Exception e) {
session = inMemorySession;
if (isInteractive) {
output.println();
output.println("Unable to load session from " + sessionState.getAbsolutePath()
+ ", falling back to in-memory session");
output.println();
}
}
}
final ClientFactory<NiFiClient> niFiClientFactory = new NiFiClientFactory();
final ClientFactory<NiFiRegistryClient> nifiRegClientFactory = new NiFiRegistryClientFactory();
return new StandardContext.Builder()
.output(output)
.session(session)
.nifiClientFactory(niFiClientFactory)
.nifiRegistryClientFactory(nifiRegClientFactory)
.interactive(isInteractive)
.build();
}
protected static void printHeader(final String bannerFile, final PrintStream output) throws IOException {
try (final InputStream bannerInput = CLIMain.class.getClassLoader().getResourceAsStream(bannerFile)) {
final String bannerContent = IOUtils.toString(bannerInput, StandardCharsets.UTF_8);
output.println(bannerContent);
output.println();
output.println("Type 'help' to see a list of available commands, use tab to auto-complete.");
output.println();
}
}
}