org.seedstack.seed.cli.SeedRunner.java Source code

Java tutorial

Introduction

Here is the source code for org.seedstack.seed.cli.SeedRunner.java

Source

/**
 * Copyright (c) 2013-2015 by The SeedStack authors. All rights reserved.
 *
 * This file is part of SeedStack, An enterprise-oriented full development stack.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.seed.cli;

import com.google.common.base.Joiner;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
import io.nuun.kernel.api.Kernel;
import io.nuun.kernel.core.NuunCore;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.seedstack.seed.cli.api.CliArgs;
import org.seedstack.seed.cli.api.CliErrorCode;
import org.seedstack.seed.cli.api.CliOption;
import org.seedstack.seed.cli.api.CommandLineHandler;
import org.seedstack.seed.cli.internal.CommandLinePlugin;
import org.seedstack.seed.cli.spi.CliContext;
import org.seedstack.seed.core.api.Application;
import org.seedstack.seed.core.api.SeedException;
import org.seedstack.seed.core.internal.CorePlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * This class executes {@link org.seedstack.seed.cli.api.CommandLineHandler}s found in the classpath.
 *
 * @author epo.jemba@ext.mpsa.com
 * @author adrien.lauer@mpsa.com
 */
public final class SeedRunner {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeedRunner.class);

    private SeedRunner() {
    }

    /**
     * Main method to run Seed CLI application.
     *
     * @param args the command line arguments.
     */
    public static void main(String[] args) throws Exception {
        int returnCode = -1;

        LOGGER.info("Starting Seed CLI application");

        try {
            returnCode = execute(args);
        } catch (SeedException e) {
            handleException(e);
            e.printStackTrace(System.err);
        } catch (Exception e) {
            handleException(e);
            SeedException.wrap(e, CliErrorCode.UNEXPECTED_EXCEPTION).printStackTrace(System.err);
        }

        // no java.lang.Error handling is done

        LOGGER.info("Stopping Seed CLI application (return code {})", returnCode);

        System.exit(returnCode);
    }

    /**
     * Execute a Seed CLI command (implemented by a {@link CommandLineHandler}.
     *
     * @param args the command line arguments. First argument is the name of the CLI command. Subsequent argument are
     *             passed to the CLI command.
     * @return the return code of the CLI command.
     * @throws Exception when the CLI command fails to complete.
     */
    public static int execute(String[] args) throws Exception {
        Kernel kernel = startKernel(new CliContext(args));

        try {
            Injector injector = kernel.objectGraph().as(Injector.class);
            String defaultCommand = injector.getInstance(Application.class).getConfiguration()
                    .getString(CommandLinePlugin.DEFAULT_COMMAND_CONFIG_KEY);
            Callable<Integer> callable;

            if (defaultCommand != null) {
                callable = new SeedCallable(defaultCommand, args);
            } else {
                if (args == null || args.length == 0 || args[0].isEmpty()) {
                    throw SeedException.createNew(CliErrorCode.NO_COMMAND_SPECIFIED);
                }

                // A command must be provided as first argument, it is extracted from the command line
                String[] effectiveArgs = new String[args.length - 1];
                System.arraycopy(args, 1, effectiveArgs, 0, effectiveArgs.length);

                callable = new SeedCallable(args[0], effectiveArgs);
            }

            injector.injectMembers(callable);
            return callable.call();
        } finally {
            stopKernel(kernel);
        }
    }

    /**
     * Method to execute a callable as a CLI application.
     *
     * @param args     the command line arguments.
     * @param callable the callable to execute
     * @return the return code of the callable
     * @throws Exception when the CLI command fails to complete.
     */
    public static int execute(String[] args, Callable<Integer> callable) throws Exception {
        Kernel kernel = startKernel(new CliContext(args));

        try {
            kernel.objectGraph().as(Injector.class).injectMembers(callable);
            return callable.call();
        } finally {
            stopKernel(kernel);
        }
    }

    private static Kernel startKernel(CliContext args) {
        Kernel kernel = NuunCore.createKernel(NuunCore.newKernelConfiguration().containerContext(args));
        kernel.init();
        kernel.start();
        return kernel;
    }

    private static void stopKernel(Kernel kernel) {
        if (kernel.isStarted()) {
            kernel.stop();
        }
    }

    private static void handleException(Exception e) {
        LOGGER.error("An exception occurred during CLI application startup, collecting diagnostic information");
        CorePlugin.getDiagnosticManager().dumpDiagnosticReport(e);
    }

    public static class SeedCallable implements Callable<Integer> {
        private final String cliCommand;
        private final String[] args;

        @Inject
        private Injector injector;

        public SeedCallable(String cliCommand, String[] args) {
            this.cliCommand = cliCommand;
            this.args = args;
        }

        @Override
        public Integer call() throws Exception {
            try {
                CommandLineHandler commandLineHandler = injector
                        .getInstance(Key.get(CommandLineHandler.class, Names.named(cliCommand)));
                injectCommandLineHandler(commandLineHandler);

                LOGGER.info("Executing CLI command {}, handled by {}", cliCommand,
                        commandLineHandler.getClass().getCanonicalName());

                return commandLineHandler.call();
            } catch (ConfigurationException e) {
                throw SeedException.wrap(e, CliErrorCode.COMMAND_LINE_HANDLER_NOT_FOUND).put("commandLineHandler",
                        cliCommand);
            }
        }

        private void injectCommandLineHandler(CommandLineHandler commandLineHandler) {
            Options options = new Options();
            List<CliOption> optionAnnotations = new ArrayList<CliOption>();
            List<Field> optionFields = new ArrayList<Field>();
            Field argsField = null;
            int mandatoryArgsCount = 0;

            // Compute injection info
            for (Field field : commandLineHandler.getClass().getDeclaredFields()) {
                CliOption optionAnnotation = field.getAnnotation(CliOption.class);
                CliArgs argsAnnotation = field.getAnnotation(CliArgs.class);

                if (optionAnnotation != null) {
                    Option option = new Option(optionAnnotation.name(), optionAnnotation.longName(),
                            optionAnnotation.valueCount() > 0 || optionAnnotation.valueCount() == -1,
                            optionAnnotation.description());

                    if (optionAnnotation.valueCount() == -1) {
                        option.setArgs(Option.UNLIMITED_VALUES);
                    } else if (optionAnnotation.valueCount() > 0) {
                        option.setArgs(optionAnnotation.valueCount());
                    }

                    option.setValueSeparator(optionAnnotation.valueSeparator());

                    optionAnnotations.add(optionAnnotation);
                    optionFields.add(field);
                    options.addOption(option);
                } else if (argsAnnotation != null) {
                    mandatoryArgsCount = argsAnnotation.mandatoryCount();
                    argsField = field;
                }
            }

            // Parse command line
            CommandLineParser parser = new PosixParser();
            CommandLine commandLine;
            try {
                commandLine = parser.parse(options, args);
            } catch (ParseException e) {
                throw SeedException.wrap(e, CliErrorCode.ERROR_PARSING_COMMAND_LINE).put("command", cliCommand)
                        .put("commandLine", Joiner.on(' ').join(args));
            }

            // Inject options
            for (int i = 0; i < optionAnnotations.size(); i++) {
                CliOption cliOption = optionAnnotations.get(i);
                Field field = optionFields.get(i);
                String[] value = null;

                if (cliOption.valueCount() > 0 || cliOption.valueCount() == -1) {
                    if (commandLine.hasOption(cliOption.name())) {
                        value = commandLine.getOptionValues(cliOption.name());
                    } else {
                        if (cliOption.mandatory()) {
                            if (cliOption.defaultValues().length == 0) {
                                throw SeedException.createNew(CliErrorCode.MISSING_MANDATORY_OPTION)
                                        .put("command", cliCommand).put("option", cliOption.name());
                            } else {
                                value = cliOption.defaultValues();
                            }
                        } else if (cliOption.defaultValues().length != 0) {
                            value = cliOption.defaultValues();
                        }
                    }

                    if (value != null) {
                        try {
                            Class<?> fieldType = field.getType();

                            field.setAccessible(true);

                            if (String.class.isAssignableFrom(fieldType)) {
                                field.set(commandLineHandler, value[0]);
                            } else if (fieldType.isArray()
                                    && String.class.isAssignableFrom(fieldType.getComponentType())) {
                                field.set(commandLineHandler, value);
                            } else if (Map.class.isAssignableFrom(fieldType)) {
                                field.set(commandLineHandler, buildOptionArgumentMap(cliOption.name(), value));
                            } else {
                                throw SeedException.createNew(CliErrorCode.UNSUPPORTED_OPTION_FIELD_TYPE)
                                        .put("command", cliCommand).put("fieldType", fieldType.getCanonicalName());
                            }
                        } catch (IllegalAccessException e) {
                            throw SeedException.wrap(e, CliErrorCode.UNABLE_TO_INJECT_OPTION)
                                    .put("command", cliCommand).put("option", cliOption.name());
                        }
                    }
                } else {
                    try {
                        field.setAccessible(true);
                        field.set(commandLineHandler, commandLine.hasOption(cliOption.name()));
                    } catch (IllegalAccessException e) {
                        throw SeedException.wrap(e, CliErrorCode.UNABLE_TO_INJECT_OPTION).put("command", cliCommand)
                                .put("option", cliOption.name());
                    }
                }
            }

            // Inject args
            if (argsField != null) {
                if (commandLine.getArgs().length < mandatoryArgsCount) {
                    throw SeedException.createNew(CliErrorCode.MISSING_ARGUMENTS).put("command", cliCommand)
                            .put("required", mandatoryArgsCount).put("given", commandLine.getArgs().length);
                } else {
                    argsField.setAccessible(true);
                    try {
                        argsField.set(commandLineHandler, commandLine.getArgs());
                    } catch (IllegalAccessException e) {
                        throw SeedException.createNew(CliErrorCode.UNABLE_TO_INJECT_ARGUMENTS).put("command",
                                cliCommand);
                    }
                }
            }
        }

        private Map<String, String> buildOptionArgumentMap(String optionName, String[] optionArguments) {
            Map<String, String> optionArgumentsMap = new HashMap<String, String>();

            if (optionArguments.length % 2 == 0) {
                for (int i = 0; i < optionArguments.length; i = i + 2) {
                    optionArgumentsMap.put(optionArguments[i], optionArguments[i + 1]);
                }
            } else {
                throw SeedException.createNew(CliErrorCode.ODD_NUMBER_OF_OPTION_ARGUMENTS)
                        .put("command", cliCommand).put("option", optionName);
            }

            return optionArgumentsMap;
        }
    }
}