All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.jct.demo.DemoMain Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version

/*
 * Copyright (C) 2023 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.jct.demo;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import org.dellroad.jct.core.ConsoleSession;
import org.dellroad.jct.core.ExecSession;
import org.dellroad.jct.core.ShellRequest;
import org.dellroad.jct.core.ShellSession;
import org.dellroad.jct.core.simple.CommandBundle;
import org.dellroad.jct.core.simple.SimpleCommandSupport;
import org.dellroad.jct.core.simple.SimpleExec;
import org.dellroad.jct.core.simple.SimpleExecRequest;
import org.dellroad.jct.core.simple.SimpleShell;
import org.dellroad.jct.core.simple.SimpleShellRequest;
import org.dellroad.jct.core.simple.command.HelpCommand;
import org.dellroad.jct.core.util.ConsoleUtil;
import org.dellroad.jct.jshell.JShellShell;
import org.dellroad.jct.jshell.JShellShellSession;
import org.dellroad.jct.jshell.command.JShellCommand;
import org.dellroad.jct.ssh.simple.SimpleConsoleSshServer;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;

/**
 * Demonstration of various Java Console Toolkit functionality.
 */
public class DemoMain {

    private static DemoMain instance;

    private final List commandBundles = CommandBundle.scanAndGenerate().collect(Collectors.toList());

    public DemoMain() {
        // Replace standard "jshell" command (if present) with our custom version
        commandBundles.forEach(bundle -> bundle.computeIfPresent("jshell", (name, value) -> new DemoJShellCommand()));
    }

    public String getName() {
        return "jct-demo";
    }

    public static DemoMain getInstance() {
        return DemoMain.instance;
    }

    public int run(String[] args) {

        // Parse command line
        File sshHostKeyFile = this.getDefaultHostKeyFile();
        File sshAuthKeysFile = this.getDefaultAuthKeysFile();
        int sshListenPort = this.getDefaultListenPort();
        final ArrayDeque params = new ArrayDeque<>(Arrays.asList(args));
        boolean ssh = false;
        boolean console = true;
    argLoop:
        while (!params.isEmpty() && params.peekFirst().startsWith("-")) {
            String option = params.removeFirst();
            switch (option) {
            case "--no-console":
                console = false;
                break;
            case "--ssh":
                ssh = true;
                break;
            case "--ssh-auth-keys-file":
                if (params.isEmpty()) {
                    this.usage(System.err);
                    return 1;
                }
                sshAuthKeysFile = new File(params.removeFirst());
                break;
            case "--ssh-host-key-file":
                if (params.isEmpty()) {
                    this.usage(System.err);
                    return 1;
                }
                sshHostKeyFile = new File(params.removeFirst());
                break;
            case "--ssh-listen-port":
                if (params.isEmpty()) {
                    this.usage(System.err);
                    return 1;
                }
                final String portString = params.removeFirst();
                try {
                    sshListenPort = Integer.parseInt(portString, 10);
                    if (sshListenPort < 1 || sshListenPort > 65535)
                        throw new IllegalArgumentException();
                } catch (IllegalArgumentException e) {
                    System.err.println(String.format("%s: invalid SSH listen port \"%s\"", this.getName(), portString));
                    this.usage(System.err);
                    return 1;
                }
                break;
            case "--help":
                this.usage(System.err);
                return 0;
            case "--":
                break argLoop;
            default:
                System.err.println(String.format("%s: unknown option \"%s\"", this.getName(), option));
                this.usage(System.err);
                return 1;
            }
        }

        // Create and configure console components
        final SimpleExec exec = new SimpleExec();
        final SimpleShell shell = new SimpleShell();
        exec.getCommandBundles().addAll(this.commandBundles);
        shell.getCommandBundles().addAll(this.commandBundles);

        // Interactive shell or execute command directly?
        final ConsoleSession session;
        if (!params.isEmpty()) {

            // Sanity check
            if (ssh) {
                System.err.println(String.format(
                  "%s: SSH server is not compatible with direct command execution", "Error", params.getFirst()));
                return 1;
            }

            // Create exec session
            session = this.createExecSession(exec, params);
        } else {

            // Start SSH server
            if (ssh) {
                SimpleConsoleSshServer server = SimpleConsoleSshServer.builder()
                  .exec(exec)
                  .shell(shell)
                  .hostKey(sshHostKeyFile.toPath())
                  .authorizedKeys(sshAuthKeysFile.toPath())
                  .listenPort(sshListenPort)
                  .loopbackOnly(true)
                  .build();
                try {
                    server.start();
                } catch (IOException e) {
                    System.err.println(String.format("%s: error starting SSH server", this.getName()));
                    e.printStackTrace(System.err);
                    return 1;
                }
                System.err.println(String.format("%s: started SSH server on port %d", this.getName(), sshListenPort));
            }

            // Start console, or else just sleep
            session = console ?
              this.createShellSession(shell) :
              this.createExecSession(exec, Arrays.asList("sleep", "99999999"));
        }

        // Check for session creation failure
        if (session == null)
            return 1;

        // Execute session, if any, otherwise just hang
        try {
            return session.execute();
        } catch (InterruptedException e) {
            System.err.println(String.format("%s: interrupted", this.getName()));
            return 1;
        }
    }

    private ExecSession createExecSession(SimpleExec exec, Collection params) {

        // Create request
        final SimpleExecRequest request = new SimpleExecRequest(System.in,
          System.out, System.err, System.getenv(), new ArrayList<>(params));

        // Find command
        final SimpleCommandSupport.FoundCommand foundCommand = exec.findCommand(System.err, request);
        if (foundCommand == null)
            return null;

        // Create exec session
        try {
            return exec.newExecSession(request, foundCommand);
        } catch (IOException e) {
            System.err.println(String.format("%s: error creating %s session: %s", this.getName(), "exec", e));
            return null;
        }
    }

    private ShellSession createShellSession(SimpleShell shell) {

        // Create system terminal
        final AtomicReference shellSessionRef = new AtomicReference<>();
        final Terminal terminal;
        try {
            terminal = TerminalBuilder.builder()
              .name("JCT")
              .system(true)
              .nativeSignals(true)
              .signalHandler(ConsoleUtil.interrruptHandler(shellSessionRef::get, Terminal.SignalHandler.SIG_DFL))
              .build();
        } catch (IOException e) {
            System.err.println(String.format("%s: error creating system terminal: %s", this.getName(), e));
            return null;
        }

        // Create shell session
        final ShellSession shellSession;
        try {
            shellSession = shell.newShellSession(
              new SimpleShellRequest(terminal, Collections.emptyList(), System.getenv()));
        } catch (IOException e) {
            System.err.println(String.format("%s: error creating %s session: %s", this.getName(), "shell", e));
            return null;
        }
        shellSessionRef.set(shellSession);
        return shellSession;
    }

    public void usage(PrintStream out) {
        out.println(String.format("Usage:"));
        out.println(String.format("    %s [options] [command ...]", this.getName()));
        out.println();
        out.println(String.format("Options:"));
        out.println(String.format(
          "    --no-console                 Don't start command line console"));
        out.println(String.format(
          "    --ssh                        Enable SSH server"));
        out.println(String.format(
          "    --ssh-auth-keys-file path    Specify SSH authorized users file (default %s)", this.getDefaultAuthKeysFile()));
        out.println(String.format(
          "    --ssh-host-key-file path     Specify SSH host key file (default %s)", this.getDefaultHostKeyFile()));
        out.println(String.format(
          "    --ssh-listen-port port       Specify SSH server TCP port (default %d)", this.getDefaultListenPort()));
        out.println(String.format(
          "    --help                       Display this usage message"));
        out.println();
        out.println(String.format("Commands:"));
        HelpCommand.listCommands(out, this.commandBundles);
    }

    public File getDefaultAuthKeysFile() {
        String file = "authorized_keys";
        final String homeDir = System.getProperty("user.home");
        if (homeDir != null) {
            final String fs = System.getProperty("file.separator");
            file = String.format("%s%s%s%s%s", homeDir, fs, ".ssh", fs, file);
        }
        return new File(file);
    }

    public File getDefaultHostKeyFile() {
        return new File("hostkey");
    }

    public int getDefaultListenPort() {
        return 9191;
    }

    public static void main(String[] args) {

        // Create demo singleton
        final DemoMain demo = new DemoMain();
        DemoMain.instance = demo;

        // Run the demo
        int exitValue;
        try {
            exitValue = demo.run(args);
        } catch (RuntimeException e) {
            e.printStackTrace(System.err);
            exitValue = 1;
        }

        // Done
        System.exit(exitValue);
    }

// DemoJShellCommand

    private static class DemoJShellCommand extends JShellCommand {
        DemoJShellCommand() {
            super(new JShellShell() {
                @Override
                public JShellShellSession newShellSession(ShellRequest request) {
                    final JShellShellSession session = new JShellShellSession(this, request) {

                        // Add startup script to the jshell command line (if found)
                        @Override
                        protected List modifyJShellParams(List params) {
                            final File startupFile = new File("startup.jsh");
                            if (startupFile.exists()) {
                                params = new ArrayList<>(params);
                                params.add(0, "--startup");
                                params.add(1, startupFile.toString());
                            }
                            return super.modifyJShellParams(params);
                        }
                    };

                    // Configure local execution
                    session.setLocalContextClassLoader(Thread.currentThread().getContextClassLoader());
                    return session;
                }
            });
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy