blob: 5fc9aa0d0c94effce92004d758b5dc9474747910 [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.sis.console;
import java.util.Locale;
import java.util.logging.LogManager;
import java.util.logging.ConsoleHandler;
import java.io.Console;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.sql.SQLException;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.MonolineFormatter;
/**
* Command line interface for Apache SIS. The {@link #main(String[])} method accepts the following actions:
*
* <blockquote><table class="compact">
* <caption>Supported command-line actions</caption>
* <tr><td>{@code help} </td><td>Show a help overview.</td></tr>
* <tr><td>{@code about} </td><td>Show information about Apache SIS and system configuration.</td></tr>
* <tr><td>{@code mime-type} </td><td>Show MIME type for the given file.</td></tr>
* <tr><td>{@code metadata} </td><td>Show metadata information for the given file.</td></tr>
* <tr><td>{@code crs} </td><td>Show Coordinate Reference System information for the given file or code.</td></tr>
* <tr><td>{@code identifier} </td><td>Show identifiers for metadata and referencing systems in the given file.</td></tr>
* <tr><td>{@code transform} </td><td>Convert or transform coordinates from given source CRS to target CRS.</td></tr>
* </table></blockquote>
*
* Each command can accepts some of the following options:
*
* <blockquote><table class="compact">
* <caption>Supported command-line options</caption>
* <tr><td>{@code --sourceCRS} </td><td>The Coordinate Reference System of input data.</td></tr>
* <tr><td>{@code --targetCRS} </td><td>The Coordinate Reference System of output data.</td></tr>
* <tr><td>{@code --format} </td><td>The output format: {@code xml}, {@code wkt}, {@code wkt1} or {@code text}.</td></tr>
* <tr><td>{@code --locale} </td><td>The locale to use for the command output.</td></tr>
* <tr><td>{@code --timezone} </td><td>The timezone for the dates to be formatted.</td></tr>
* <tr><td>{@code --encoding} </td><td>The encoding to use for the command outputs and some inputs.</td></tr>
* <tr><td>{@code --colors} </td><td>Whether colorized output shall be enabled.</td></tr>
* <tr><td>{@code --brief} </td><td>Whether the output should contains only brief information.</td></tr>
* <tr><td>{@code --verbose} </td><td>Whether the output should contains more detailed information.</td></tr>
* <tr><td>{@code --debug} </td><td>Prints full stack trace in case of failure.</td></tr>
* <tr><td>{@code --help} </td><td>Lists the options available for a specific command.</td></tr>
* </table></blockquote>
*
* The {@code --locale}, {@code --timezone} and {@code --encoding} options apply to the command output sent
* to the {@linkplain System#out standard output stream}, but usually do not apply to the error messages sent
* to the {@linkplain System#err standard error stream}. The reason is that command output may be targeted to
* a client, while the error messages are usually for the operator.
*
* <div class="section">SIS installation on remote machines</div>
* Some sub-commands can operate on SIS installation on remote machines, provided that remote access has been enabled
* at the Java Virtual Machine startup time. See {@linkplain org.apache.sis.console package javadoc} for more information.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.3
* @module
*/
public final class Command {
/**
* The code given to {@link System#exit(int)} when the program failed because of a unknown sub-command.
*/
public static final int INVALID_COMMAND_EXIT_CODE = 1;
/**
* The code given to {@link System#exit(int)} when the program failed because of a unknown option.
* The set of valid options depend on the sub-command to execute.
*/
public static final int INVALID_OPTION_EXIT_CODE = 2;
/**
* The code given to {@link System#exit(int)} when the program failed because of an illegal user argument.
* The user arguments are everything which is not a command name or an option. They are typically file names,
* but can occasionally be other types like URL.
*/
public static final int INVALID_ARGUMENT_EXIT_CODE = 3;
/**
* The code given to {@link System#exit(int)} when a file given in argument uses an unknown file format.
*/
public static final int UNKNOWN_STORAGE_EXIT_CODE = 4;
/**
* The code given to {@link System#exit(int)} when the program failed because of an {@link java.io.IOException}.
*/
public static final int IO_EXCEPTION_EXIT_CODE = 100;
/**
* The code given to {@link System#exit(int)} when the program failed because of an {@link java.sql.SQLException}.
*/
public static final int SQL_EXCEPTION_EXIT_CODE = 101;
/**
* The code given to {@link System#exit(int)} when the program failed for a reason
* other than the ones enumerated in the above constants.
*/
public static final int OTHER_ERROR_EXIT_CODE = 199;
/**
* The sub-command to execute.
*/
private final CommandRunner command;
/**
* Creates a new command for the given arguments. The first value in the given array which is
* not an option is taken as the command name. All other values are options or filenames.
*
* @param args the command-line arguments.
* @throws InvalidCommandException if an invalid command has been given.
* @throws InvalidOptionException if the given arguments contain an invalid option.
*/
protected Command(final String[] args) throws InvalidCommandException, InvalidOptionException {
int commandIndex = -1;
String commandName = null;
for (int i=0; i<args.length; i++) {
final String arg = args[i];
if (arg.startsWith(Option.PREFIX)) {
final String name = arg.substring(Option.PREFIX.length());
final Option option = Option.forLabel(name);
if (option.hasValue) {
i++; // Skip the next argument.
}
} else {
// Takes the first non-argument option as the command name.
commandName = arg;
commandIndex = i;
break;
}
}
if (commandName == null) {
command = new HelpCommand(-1, args);
} else {
switch (commandName.toLowerCase(Locale.US)) {
case "help": command = new HelpCommand (commandIndex, args); break;
case "about": command = new AboutCommand (commandIndex, args); break;
case "mime-type": command = new MimeTypeCommand (commandIndex, args); break;
case "metadata": command = new MetadataCommand (commandIndex, args); break;
case "crs": command = new CRSCommand (commandIndex, args); break;
case "identifier": command = new IdentifierCommand(commandIndex, args); break;
case "transform": command = new TransformCommand (commandIndex, args); break;
default: throw new InvalidCommandException(Errors.format(
Errors.Keys.UnknownCommand_1, commandName), commandName);
}
}
CommandRunner.instance = command; // For ResourcesDownloader only.
}
/**
* Runs the command. If an exception occurs, then the exception message is sent to the error output stream
* before to be thrown. Callers can map the exception to a {@linkplain System#exit(int) system exit code}
* by the {@link #exitCodeFor(Throwable)} method.
*
* @return 0 on success, or an exit code if the command failed for a reason other than a Java exception.
* @throws Exception if an error occurred during the command execution. This is typically, but not limited, to
* {@link IOException}, {@link SQLException}, {@link DataStoreException} or {@link TransformException}.
*/
public int run() throws Exception {
if (command.hasContradictoryOptions(Option.BRIEF, Option.VERBOSE)) {
return INVALID_OPTION_EXIT_CODE;
}
if (command.options.containsKey(Option.HELP)) {
command.help(command.commandName.toLowerCase(Locale.US));
} else try {
return command.run();
} catch (Exception e) {
command.error(null, e);
throw e;
}
return 0;
}
/**
* Returns the exit code for the given exception, or 0 if unknown. This method iterates through the
* {@linkplain Throwable#getCause() causes} until an exception matching a {@code *_EXIT_CODE}
* constant is found.
*
* @param cause the exception for which to get the exit code.
* @return the exit code as one of the {@code *_EXIT_CODE} constant, or {@link #OTHER_ERROR_EXIT_CODE} if unknown.
*/
public static int exitCodeFor(Throwable cause) {
while (cause != null) {
if (cause instanceof InvalidCommandException) return INVALID_COMMAND_EXIT_CODE;
if (cause instanceof InvalidOptionException) return INVALID_OPTION_EXIT_CODE;
if (cause instanceof IOException) return IO_EXCEPTION_EXIT_CODE;
if (cause instanceof SQLException) return SQL_EXCEPTION_EXIT_CODE;
cause = cause.getCause();
}
return OTHER_ERROR_EXIT_CODE;
}
/**
* Prints the message of the given exception. This method is invoked only when the error occurred before
* the {@link CommandRunner} has been built, otherwise the {@link CommandRunner#err} printer shall be used.
*
* @param args the command line arguments, used only for detecting if the {@code --debug} option was present.
*/
private static void error(final String[] args, final Exception e) {
final boolean debug = ArraysExt.containsIgnoreCase(args, Option.PREFIX + "debug");
final Console console = System.console();
if (console != null) {
final PrintWriter err = console.writer();
if (debug) {
e.printStackTrace(err);
} else {
err.println(e.getLocalizedMessage());
}
err.flush();
} else {
@SuppressWarnings("UseOfSystemOutOrSystemErr")
final PrintStream err = System.err;
if (debug) {
e.printStackTrace(err);
} else {
err.println(e.getLocalizedMessage());
}
err.flush();
}
}
/**
* Prints the information to the standard output stream.
*
* @param args command-line options.
*/
public static void main(final String[] args) {
/*
* The logging configuration is given by the "conf/logging.properties" file in the Apache SIS
* installation directory. By default, that configuration file contains the following line:
*
* java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter
*
* However this configuration is silently ignored by LogManager at JVM startup time, probably
* because the Apache SIS class is not on the system classpath. So we check again for this
* configuration line here, and manually install our log formatter only if the above-cited
* line is present.
*/
final LogManager manager = LogManager.getLogManager();
if (MonolineFormatter.class.getName().equals(manager.getProperty(ConsoleHandler.class.getName() + ".formatter"))) {
MonolineFormatter.install();
}
/*
* Now run the command.
*/
final Command c;
try {
c = new Command(args);
} catch (InvalidCommandException e) {
error(args, e);
System.exit(INVALID_COMMAND_EXIT_CODE);
return;
} catch (InvalidOptionException e) {
error(args, e);
System.exit(INVALID_OPTION_EXIT_CODE);
return;
}
int status;
try {
status = c.run();
} catch (Exception e) {
status = exitCodeFor(e);
}
if (status != 0) {
System.exit(status);
}
}
}