org.goobi.eadmgr.Cli.java Source code

Java tutorial

Introduction

Here is the source code for org.goobi.eadmgr.Cli.java

Source

/*
 * This file is part of the Goobi Application - a Workflow tool for the support of
 * mass digitization.
 *
 * Visit the websites for more information.
 *     - http://gdz.sub.uni-goettingen.de
 *     - http://www.goobi.org
 *     - http://launchpad.net/goobi-production
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License 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; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA 02111-1307 USA
 */
package org.goobi.eadmgr;

import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXParseException;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.*;

import static org.apache.activemq.ActiveMQConnection.DEFAULT_BROKER_URL;

class Cli extends CliBase {

    public static final String DEFAULT_PROCESS_TEMPLATE = "Schlegel";
    public static final String DEFAULT_DOCTYPE = "multivolume";
    public static final String DEFAULT_SUBJECT_QUEUE = "GoobiProduction.createNewProcessWithLogicalStructureData.Queue";
    public static final String DEFAULT_RESULT_TOPIC = "GoobiProduction.ResultMessages.Topic";
    public static final String ACTIVEMQ_CONFIGURING_URL = "http://activemq.apache.org/cms/configuring.html";
    public static final String PROMPT_HINT = "Try 'eadmgr -h' for more information.";
    public static final String DEFAULT_EXTRACTION_PROFILE = "schlegel.xsl";
    public static final String IMPLEMENTATION_VERSION = Cli.class.getPackage().getImplementationVersion();
    private String[] args;
    private Options options;
    private File eadFile;
    private String brokerUrl;
    private String doctype;
    private String template;
    private String folderId;
    private boolean isDryRun;
    private boolean isValidateOption;
    private Collection<String> collections;
    private Logger logger;
    private String subjectQueue;
    private Commands command;
    private boolean isUseFolderId;
    private Map<String, String> userMessageFields;
    private String topicQueue;
    private String extractionProfile;

    public static void main(String[] args) {
        Cli cli = new Cli();
        System.exit(cli.run(args));
    }

    private void println(String msg) {
        System.out.println(msg);
    }

    private void printList(List<String> list) {
        for (String s : list) {
            println(s);
        }
    }

    @Override
    @SuppressWarnings("AccessStaticViaInstance") // workaround for screwed OptionBuilder API
    public void initOptions() {
        options = new Options();

        // mutually exclusive main commands
        OptionGroup mainCommands = new OptionGroup();
        mainCommands.addOption(new Option("h", "help", false, "Print this usage information"));
        mainCommands.addOption(
                new Option("l", "list-folder-ids", false, "List all folder IDs available for process creation."));
        mainCommands.addOption(new Option("c", "create-process", true,
                "Extracted data for given folder ID as process creation message to configured ActiveMQ server."));
        options.addOptionGroup(mainCommands);

        // additional switches
        options.addOption(OptionBuilder.withLongOpt("validate").withDescription(
                "Validate XML structure of the EAD document. Exits with error code 1 if validation fails. Can be used without other commands for just validating files.")
                .create());
        options.addOption(OptionBuilder.withLongOpt("dry-run")
                .withDescription("Print folder information instead of sending it.").create());
        options.addOption("v", "verbose", false, "Be verbose about what is going on.");
        options.addOption("u", "url", true,
                MessageFormat.format("ActiveMQ Broker URL. If not given the broker is contacted at \"{0}\".\n"
                        + "Note that using the failover protocol will block the program forever if the ActiveMQ host is not reachable unless you specify the \"timeout\" parameter in the URL. See {1} for more information.",
                        DEFAULT_BROKER_URL, ACTIVEMQ_CONFIGURING_URL));
        options.addOption("q", "queue", true, MessageFormat.format(
                "ActiveMQ Subject Queue. If not given messages get enqueue at \"{0}\".", DEFAULT_SUBJECT_QUEUE));
        options.addOption(OptionBuilder.withLongOpt("topic-queue")
                .withDescription(MessageFormat.format(
                        "ActiveMQ result topic Queue. If not given wait for result message posting at \"{0}\".",
                        DEFAULT_RESULT_TOPIC))
                .hasArg().create());
        options.addOption("t", "template", true, MessageFormat
                .format("Goobi Process Template name. If not given \"{0}\" is used.", DEFAULT_PROCESS_TEMPLATE));
        options.addOption("d", "doctype", true,
                MessageFormat.format("Goobi Doctype name. If not given \"{0}\" is used.", DEFAULT_DOCTYPE));
        options.addOption(OptionBuilder.withLongOpt("use-folder-id").withDescription(
                "Use folder ID when generating ActiveMQ Message IDs. If not given a random 128 bit universally unique identifier (UUID) is generated.")
                .create());
        options.addOption(OptionBuilder.withLongOpt("collection")
                .withDescription("Name of a collection to which the newly created process should be assigned.")
                .hasArg().create("C"));
        options.addOption(OptionBuilder
                .withDescription(
                        "User defined option in the form of <key>=<value> to append to the ActiveMQ message.")
                .hasArgs().create("O"));
        options.addOption("x", "extraction-profile", true, MessageFormat.format(
                "XSLT EAD extraction profile name. Either an absolute pathname or a file that can be found on the classpath. If not given \"{0}\" is used.",
                DEFAULT_EXTRACTION_PROFILE));
    }

    public void parseArguments(String[] args) throws Exception {
        CommandLineParser parser = new BasicParser();
        this.args = args;
        CommandLine cmdl = parser.parse(options, args);

        determineCommand(cmdl);

        // stop parsing the rest of the arguments if user requests help
        if (command == Commands.Help) {
            return;
        }

        brokerUrl = cmdl.getOptionValue("u", DEFAULT_BROKER_URL);
        subjectQueue = cmdl.getOptionValue("q", DEFAULT_SUBJECT_QUEUE);
        topicQueue = cmdl.getOptionValue("result-topic", DEFAULT_RESULT_TOPIC);
        doctype = cmdl.getOptionValue("d", DEFAULT_DOCTYPE);
        isDryRun = cmdl.hasOption("dry-run");
        isValidateOption = cmdl.hasOption("validate");
        template = cmdl.getOptionValue("t", DEFAULT_PROCESS_TEMPLATE);
        isUseFolderId = cmdl.hasOption("use-folder-id");
        userMessageFields = splitAndMap(cmdl.getOptionValues("O"));
        extractionProfile = cmdl.getOptionValue("x", DEFAULT_EXTRACTION_PROFILE);

        if (command == Commands.Create) {
            folderId = cmdl.getOptionValue('c');
            collections = new ArrayList<String>();
            String[] optVal = cmdl.getOptionValues("collection");
            if (optVal != null) {
                collections.addAll(Arrays.asList(optVal));
            }
            if (collections.isEmpty()) {
                throw new Exception(
                        "Option 'create-process' requires option 'collection' to be properly specified.");
            }
        }

        String[] leftOverArgs = cmdl.getArgs();
        if (leftOverArgs.length == 0) {
            throw new Exception("No filename given.");
        }
        if (leftOverArgs.length > 1) {
            throw new Exception("Only one filename allowed.");
        }

        this.eadFile = new File(leftOverArgs[0]);
        if (!eadFile.exists() || !eadFile.canRead() || !eadFile.isFile()) {
            throw new Exception("Cannot read " + eadFile.getAbsolutePath());
        }

        boolean verbose = cmdl.hasOption('v');
        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", verbose ? "TRACE" : "INFO");
        logger = LoggerFactory.getLogger(Cli.class);
    }

    private Map<String, String> splitAndMap(String[] optionValues) throws Exception {
        if (optionValues != null) {
            HashMap<String, String> result = new HashMap<String, String>();
            for (String s : optionValues) {
                String[] split = s.split("=");
                if ((split.length == 2) && (!split[0].isEmpty()) && (!split[1].isEmpty())) {
                    result.put(split[0], split[1]);
                } else {
                    throw new Exception("Invalid argument for option: -O " + s);
                }
            }
            return result;
        }
        return null;
    }

    private void determineCommand(CommandLine cmdl) {
        if (cmdl.hasOption('h')) {
            command = Commands.Help;
        } else if (cmdl.hasOption("l")) {
            command = Commands.List;
        } else if (cmdl.hasOption("c")) {
            command = Commands.Create;
        } else if (cmdl.hasOption("validate")) {
            command = Commands.Validate;
        } else {
            command = Commands.Help;
        }
    }

    @Override
    public int processing() throws Exception {

        if ((command == Commands.Help) || (args.length == 0)) {
            printUsageInformation();
            return 0;
        }

        logger.info("Version: " + IMPLEMENTATION_VERSION);
        logger.info("Processing " + eadFile.getAbsolutePath());

        int returnCode = 0;

        EADDocument ead = new EADDocument();
        ead.readEadFile(eadFile, isValidateOption);

        // Validation happens while reading. Any validation error will throw an exception.
        if (isValidateOption) {
            logger.info(eadFile.getAbsolutePath() + " seems to be valid according to schema.");
        }

        switch (command) {
        case List:
            List<String> folderIds = ead.getFolderIds();
            printList(folderIds);
            break;
        case Create:
            Document vd = ead.extractFolderData(folderId, extractionProfile);
            returnCode = send(vd, template, doctype, brokerUrl, collections, userMessageFields);
            break;
        case Validate:
            // If --validate option was used as the only command, just quit here.
            break;
        }

        return returnCode;
    }

    private int send(Document vd, String template, String doctype, String brokerUrl, Collection<String> collections,
            Map<String, String> userMessageFields) throws Exception {
        logger.info("Sending XML message to ActiveMQ server at {}", brokerUrl);
        logger.trace("Collections: {}", collections);
        logger.trace("Process template: {}", template);
        logger.trace("Message doctype: {}", doctype);

        String uuid = (isUseFolderId) ? folderId : String.valueOf(java.util.UUID.randomUUID());

        Map<String, Object> m = new HashMap<String, Object>();
        m.put("id", uuid);
        m.put("template", template);
        m.put("docType", doctype);
        m.put("collections", collections);
        m.put("userMessageFields", userMessageFields);
        m.put("xml", String.valueOf(XMLSerializer.serialize(vd)));

        if (isDryRun) {
            println(m.toString());
        } else {
            GoobiMQConnection conn = new GoobiMQConnection(brokerUrl, subjectQueue, topicQueue);
            Map<String, Object> result = conn.sendAndWaitForResult(m);
            conn.close();

            logger.debug(String.valueOf(result));

            if (!result.get("level").equals("success")) {
                logger.error(String.valueOf(result.get("message")));
                return 1;
            } else {
                logger.info("Process for ID {} has been successfully created.", uuid);
            }
        }

        return 0;
    }

    private void printUsageInformation() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.setWidth(120);
        formatter.printHelp("eadmgr [Options] [File]\nVersion: " + IMPLEMENTATION_VERSION, options);
    }

    @Override
    public void handleException(Exception ex) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        ex.printStackTrace(pw);

        String msg = sw.toString();

        if (logger != null) {
            if (ex instanceof SAXParseException) {
                SAXParseException sax = (SAXParseException) ex;
                logger.error(ex.getMessage() + " (at line " + sax.getLineNumber() + ")");
                return;
            } else {
                logger.error(msg);
            }
        } else {
            println(msg);
        }

        println(PROMPT_HINT);
    }

    private enum Commands {
        Help, List, Create, Validate
    }
}