org.mycore.frontend.cli.MCRCommandLineInterface.java Source code

Java tutorial

Introduction

Here is the source code for org.mycore.frontend.cli.MCRCommandLineInterface.java

Source

/*
 * $Revision$ 
 * $Date$
 * 
 * This file is part of   M y C o R e 
 * See http://www.mycore.de/ for details.
 * 
 * This program is free software; you can use it, redistribute it and / or modify it under the terms of the GNU General Public License (GPL) as published by the
 * Free Software Foundation; either version 2 of the License or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program, in a file called gpl.txt or license.txt. If not, write to the Free
 * Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
 */

package org.mycore.frontend.cli;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Element;
import org.mycore.common.MCRSession;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.MCRSystemUserInformation;
import org.mycore.common.config.MCRConfiguration;
import org.mycore.common.content.MCRJDOMContent;
import org.mycore.common.events.MCRStartupHandler;
import org.mycore.common.xml.MCRURIResolver;

/**
 * The main class implementing the MyCoRe command line interface. With the
 * command line interface, you can import, export, update and delete documents
 * and other data from/to the filesystem. Metadata is imported from and exported
 * to XML files. The command line interface is for administrative purposes and
 * to be used on the server side. It implements an interactive command prompt
 * and understands a set of commands. Each command is an instance of the class
 * <code>MCRCommand</code>.
 * 
 * @see MCRCommand
 * 
 * @author Frank Ltzenkirchen
 * @author Detlev Degenhardt
 * @author Jens Kupferschmidt
 * @author Thomas Scheffler (yagee)
 */
public class MCRCommandLineInterface {

    private static final Logger LOGGER = LogManager.getLogger();

    /** The name of the system */
    private static String system = null;

    /** A queue of commands waiting to be executed */
    protected static Vector<String> commandQueue = new Vector<String>();

    protected static Vector<String> failedCommands = new Vector<String>();

    private static boolean interactiveMode = true;

    private static boolean SKIP_FAILED_COMMAND = false;

    private static MCRCommandManager knownCommands;

    private static ThreadLocal<String> sessionId = new ThreadLocal<>();

    /**
     * The main method that either shows up an interactive command prompt or
     * reads a file containing a list of commands to be processed
     */
    public static void main(String[] args) {
        MCRStartupHandler.startUp(null/*no servlet context here*/);
        //BUG: try to track down https://bamboo.mycore.de/browse/DP-TEST-234
        if (MCRConfiguration.instance().getString("MCR.CommandLineInterface.SystemName", null) == null) {
            try {
                MCRConfiguration.instance().store(System.err,
                        "I'm going die soon, this should be my gravestone quote:");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //BUG DEBUG END
        system = MCRConfiguration.instance().getString("MCR.CommandLineInterface.SystemName") + ":";

        initSession();
        output("");
        output("Command Line Interface.");
        output("");

        output("Initializing...");
        knownCommands = new MCRCommandManager();
        output("Initialization done.");

        output("Type 'help' to list all commands!");
        output("");

        readCommandFromArguments(args);

        String command = null;

        MCRCommandPrompt prompt = new MCRCommandPrompt(system);
        while (true) {
            if (commandQueue.isEmpty()) {
                if (interactiveMode) {
                    command = prompt.readCommand();
                } else if (MCRConfiguration.instance().getString("MCR.CommandLineInterface.unitTest", "false")
                        .equals("true")) {
                    break;
                } else {
                    exit();
                }
            } else {
                command = commandQueue.firstElement();
                commandQueue.removeElementAt(0);
                System.out.println(system + "> " + command);
            }

            processCommand(command);
        }
    }

    private static void readCommandFromArguments(String[] args) {
        if (args.length > 0) {
            interactiveMode = false;

            String line = readLineFromArguments(args);
            addCommands(line);
        }
    }

    private static void addCommands(String line) {
        String[] cmds = line.split(";;");
        for (String cmd : cmds) {
            cmd = cmd.trim();
            if (!cmd.isEmpty()) {
                commandQueue.add(cmd);
            }
        }
    }

    private static String readLineFromArguments(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (String arg : args) {
            sb.append(arg).append(" ");
        }
        return sb.toString();
    }

    private static void initSession() {
        MCRSession session = MCRSessionMgr.getCurrentSession();
        session.setCurrentIP("127.0.0.1");
        session.setUserInformation(MCRSystemUserInformation.getSuperUserInstance());
        MCRSessionMgr.setCurrentSession(session);
        sessionId.set(session.getID());
        MCRSessionMgr.releaseCurrentSession();
    }

    /**
     * Processes a command entered by searching a matching command in the list
     * of known commands and executing its method.
     * 
     * @param command
     *            The command string to be processed
     */
    protected static void processCommand(String command) {

        MCRSession session = MCRSessionMgr.getSession(sessionId.get());
        MCRSessionMgr.setCurrentSession(session);

        try {
            session.beginTransaction();
            List<String> commandsReturned = knownCommands.invokeCommand(expandCommand(command));
            session.commitTransaction();
            addCommandsToQueue(commandsReturned);
        } catch (Exception ex) {
            MCRCLIExceptionHandler.handleException(ex);
            rollbackTransaction(session);
            if (SKIP_FAILED_COMMAND) {
                saveFailedCommand(command);
            } else {
                saveQueue(command);
                if (!interactiveMode) {
                    System.exit(1);
                }
                commandQueue.clear();
            }
        } finally {
            MCRSessionMgr.releaseCurrentSession();
        }
    }

    /**
     * Expands variables in a command.
     * Replaces any variables in form ${propertyName} to the value defined by {@link MCRConfiguration#getString(String)}.
     * If the property is not defined not variable replacement takes place.
     * @param command a CLI command that should be expanded
     * @return expanded command
     */
    public static String expandCommand(final String command) {
        StrSubstitutor strSubstitutor = new StrSubstitutor(MCRConfiguration.instance().getPropertiesMap());
        String expandedCommand = strSubstitutor.replace(command);
        if (!expandedCommand.equals(command)) {
            LOGGER.info(command + " --> " + expandedCommand);
        }
        return expandedCommand;
    }

    private static void rollbackTransaction(MCRSession session) {
        output("Command failed. Performing transaction rollback...");

        if (session.isTransactionActive()) {
            try {
                session.rollbackTransaction();
            } catch (Exception ex2) {
                MCRCLIExceptionHandler.handleException(ex2);
            }
        }
    }

    private static void addCommandsToQueue(List<String> commandsReturned) {
        if (commandsReturned.size() > 0) {
            output("Queueing " + commandsReturned.size() + " commands to process");

            for (int i = 0; i < commandsReturned.size(); i++) {
                commandQueue.insertElementAt(commandsReturned.get(i), i);
            }
        }
    }

    protected static void saveQueue(String lastCommand) {
        output("");
        output("The following command failed:");
        output(lastCommand);
        if (!commandQueue.isEmpty()) {
            System.out.printf(Locale.ROOT, "%s There are %s other commands still unprocessed.%n", system,
                    commandQueue.size());
        } else if (interactiveMode) {
            return;
        }
        commandQueue.add(0, lastCommand);
        saveCommandQueueToFile(commandQueue, "unprocessed-commands.txt");
    }

    private static void saveCommandQueueToFile(final Vector<String> queue, String fname) {
        output("Writing unprocessed commands to file " + fname);
        try (PrintWriter pw = new PrintWriter(new File(fname), Charset.defaultCharset().name())) {
            for (String command : queue) {
                pw.println(command);
            }
            pw.close();
        } catch (IOException ex) {
            MCRCLIExceptionHandler.handleException(ex);
        }
    }

    protected static void saveFailedCommand(String lastCommand) {
        output("");
        output("The following command failed:");
        output(lastCommand);
        if (!commandQueue.isEmpty()) {
            System.out.printf(Locale.ROOT, "%s There are %s other commands still unprocessed.%n", system,
                    commandQueue.size());
        }
        failedCommands.add(lastCommand);
    }

    protected static void handleFailedCommands() {
        if (failedCommands.size() > 0) {
            System.err.println(system + " Several command failed.");
            saveCommandQueueToFile(failedCommands, "failed-commands.txt");
        }
    }

    /**
     * Show contents of a local text file, including line numbers.
     * 
     * @param fname
     *            the filename
     */
    public static void show(String fname) throws Exception {
        AtomicInteger ln = new AtomicInteger();
        System.out.println();
        Files.readAllLines(Paths.get(fname), Charset.defaultCharset())
                .forEach(l -> System.out.printf(Locale.ROOT, "%04d: %s", ln.incrementAndGet(), l));
        System.out.println();
    }

    /**
     * Reads XML content from URIResolver and sends output to a local file.
     */
    public static void getURI(String uri, String file) throws Exception {
        Element resolved = MCRURIResolver.instance().resolve(uri);
        Element cloned = (Element) resolved.clone();
        new MCRJDOMContent(cloned).sendTo(new File(file));
    }

    /**
     * Reads a file containing a list of commands to be executed and adds them
     * to the commands queue for processing. This method implements the
     * "process ..." command.
     * 
     * @param file
     *            The file holding the commands to be processed
     * @throws IOException
     *             when the file could not be read
     * @throws FileNotFoundException
     *             when the file was not found
     */
    public static List<String> readCommandsFile(String file) throws IOException, FileNotFoundException {
        try (BufferedReader reader = Files.newBufferedReader(new File(file).toPath(), Charset.defaultCharset())) {
            output("Reading commands from file " + file);

            String line;
            List<String> list = new ArrayList<String>();
            while ((line = reader.readLine()) != null) {
                line = line.trim();

                if (line.startsWith("#") || line.isEmpty()) {
                    continue;
                }
                list.add(line);
            }
            return list;
        }
    }

    /**
     * Executes simple shell commands from inside the command line interface and
     * shows their output. This method implements commands entered beginning
     * with exclamation mark, like "! ls -l /temp"
     * 
     * @param command
     *            the shell command to be executed
     * @throws IOException
     *             when an IO error occured while catching the output returned
     *             by the command
     * @throws SecurityException
     *             when the command could not be executed for security reasons
     */
    public static void executeShellCommand(String command) throws Exception {
        MCRExternalProcess process = new MCRExternalProcess(command);
        process.run();
        output(process.getOutput().asString());
        output(process.getErrors());
    }

    /**
     * Prints out the current user.
     */
    public static void whoami() {
        MCRSession session = MCRSessionMgr.getCurrentSession();
        String userName = session.getUserInformation().getUserID();
        output("You are user " + userName);
    }

    public static void cancelOnError() {
        SKIP_FAILED_COMMAND = false;
    }

    public static void skipOnError() {
        SKIP_FAILED_COMMAND = true;
    }

    /**
     * Exits the command line interface. This method implements the "exit" and
     * "quit" commands.
     */
    public static void exit() {
        showSessionDuration();
        handleFailedCommands();
        System.exit(0);
    }

    private static void showSessionDuration() {
        MCRSession session = MCRSessionMgr.getCurrentSession();
        long duration = System.currentTimeMillis() - session.getLoginTime();
        output("Session duration: " + duration + " ms");
    }

    static void output(String message) {
        System.out.println(system + " " + message);
    }

}