/* * Created on Jul 4, 2011 * Copyright 2011 by Eduard Weissmann (edi.weissmann@gmail.com). * * This file is part of the Sejda source code * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sejda.cli; import static java.util.Optional.ofNullable; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.sejda.cli.command.CliCommand; import org.sejda.cli.exception.ArgumentValidationException; import org.sejda.cli.exception.DefaultUncaughtExceptionHandler; import org.sejda.cli.exception.ExceptionUtils; import org.sejda.core.Sejda; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Sejda command line service. Responsible for interpreting the arguments, displaying help requests or delegating execution of commands * * @author Eduard Weissmann * */ public class SejdaConsole { private static final Logger LOG = LoggerFactory.getLogger(SejdaConsole.class); private final RawArguments arguments; private final Map<CustomizableProps, String> customs; private final TaskExecutionAdapter taskExecutionAdapter; public SejdaConsole(String[] rawArguments, TaskExecutionAdapter taskExecutionAdapter, Map<CustomizableProps, String> customs) { Thread.setDefaultUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler()); this.arguments = new RawArguments(rawArguments.clone()); this.taskExecutionAdapter = taskExecutionAdapter; this.customs = ofNullable(customs).map(HashMap::new).orElseGet(HashMap::new); this.customs.putIfAbsent(CustomizableProps.APP_NAME, "Sejda Console"); Sejda.CREATOR = String.format("%s %s", this.customs.get(CustomizableProps.APP_NAME), Sejda.VERSION); } /** * Interprets and executes the console command * */ public void execute() { try { doExecute(); } catch (RuntimeException e) { reportToLogger(e); throw e; } } private void doExecute() { LOG.debug("Starting execution with arguments: '" + arguments + "'"); LOG.debug("Java version: '" + System.getProperty("java.version") + "'"); if (isNoCommandSpecified()) { if (isVersionRequest() || isLicenseRequest()) { printVersionAndLicense(); } else { printGeneralHelp(); } } else { CliCommand command = getCommandSpecified(); if (isCommandHelpRequested()) { printCommandHelp(command); } else { validateNoDuplicateCommandArguments(); try { executeCommand(command); } finally { afterCommandExecuted(); } } } LOG.debug("Completed execution"); } protected void afterCommandExecuted() { } /** * throws an exception if there are duplicate option:value pairs specified, that would override each other silently otherwise * */ private void validateNoDuplicateCommandArguments() { Map<String, Object> uniqueArguments = new HashMap<>(); for (final String eachArgument : arguments.getCommandArguments()) { if (uniqueArguments.containsKey(eachArgument) && StringUtils.startsWith(eachArgument, "-")) { throw new ArgumentValidationException("Option '" + eachArgument + "' is specified twice. Please note that the correct way to specify a list of values for an option is to repeat the values after the option, without re-stating the option name. Example: --files /tmp/file1.pdf /tmp/files2.pdf"); } uniqueArguments.put(eachArgument, eachArgument); } } private void executeCommand(CliCommand command) { getTaskExecutionAdapter().execute(command.parseTaskParameters(arguments.getCommandArguments())); } private void printCommandHelp(CliCommand command) { LOG.info(command.getHelpMessage()); } private boolean isCommandHelpRequested() { return arguments.isHelpRequest() || arguments.isEmptyCommandArguments(); } private CliCommand getCommandSpecified() { return arguments.getCliCommand(); } private void printGeneralHelp() { LOG.info(new GeneralHelpFormatter(customs).getFormattedString()); } private void printVersionAndLicense() { StringBuilder info = new StringBuilder( String.format("\n%s (Version %s)\n", customs.get(CustomizableProps.APP_NAME), Sejda.VERSION)); info.append("(see http://www.sejda.org for more information)\n\n"); try (BufferedReader buffer = new BufferedReader(new InputStreamReader( SejdaConsole.class.getResourceAsStream(customs.get(CustomizableProps.LICENSE_PATH))))) { buffer.lines().forEach(l -> { info.append(l); info.append(System.lineSeparator()); }); } catch (IOException e) { LOG.error("An error occurred while reading license information, please refer to http://www.sejda.org", e); } LOG.info(info.toString()); } private boolean isNoCommandSpecified() { return arguments.isNoCommandSpecified(); } private boolean isVersionRequest() { return arguments.isVersionRequest(); } private boolean isLicenseRequest() { return arguments.isLicenseRequest(); } TaskExecutionAdapter getTaskExecutionAdapter() { return taskExecutionAdapter; } public static final String REPORT_A_BUG_MESAGE = "\nTo report a bug, please visit http://www.sejda.org/issuetracker \nHelpful information to include when raising a bug: the input files, the command line executed and the stack trace below.\n"; /** * @param e */ private void reportToLogger(Exception e) { if (ExceptionUtils.isExpectedConsoleException(e) || ExceptionUtils.isExpectedTaskException(e)) { LOG.error(e.getMessage()); } else { LOG.error(REPORT_A_BUG_MESAGE); LOG.error(e.getMessage(), e); // unexpected } } }