| /* |
| * 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); |
| } |
| } |
| } |