org.apache.aurora.scheduler.config.CommandLine.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.aurora.scheduler.config.CommandLine.java

Source

/**
 * Licensed 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.aurora.scheduler.config;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;

import javax.security.auth.kerberos.KerberosPrincipal;

import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.IStringConverterFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import org.apache.aurora.gen.DockerParameter;
import org.apache.aurora.gen.Volume;
import org.apache.aurora.scheduler.app.SchedulerMain;
import org.apache.aurora.scheduler.app.VolumeConverter;
import org.apache.aurora.scheduler.config.converters.ClassConverter;
import org.apache.aurora.scheduler.config.converters.DataAmountConverter;
import org.apache.aurora.scheduler.config.converters.DockerParameterConverter;
import org.apache.aurora.scheduler.config.converters.InetSocketAddressConverter;
import org.apache.aurora.scheduler.config.converters.TimeAmountConverter;
import org.apache.aurora.scheduler.config.types.DataAmount;
import org.apache.aurora.scheduler.config.types.TimeAmount;
import org.apache.aurora.scheduler.http.api.security.KerberosPrincipalConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Parses command line options and populates {@link CliOptions}.
 */
public final class CommandLine {

    private static final Logger LOG = LoggerFactory.getLogger(CommandLine.class);

    // TODO(wfarner): This can go away if/when options are no longer accessed statically.
    private static CliOptions instance = null;

    private static List<Object> customOptions = Lists.newArrayList();

    private CommandLine() {
        // Utility class.
    }

    /**
     * Similar to {@link #initializeForTest()}, but resets the class to an un-parsed state.
     */
    @VisibleForTesting
    static void clearForTest() {
        instance = null;
        customOptions = Lists.newArrayList();
    }

    /**
     * Initializes static command line state - the static parsed instance, and custom options objects.
     */
    @VisibleForTesting
    public static void initializeForTest() {
        instance = new CliOptions();
        customOptions = Lists.newArrayList();
    }

    private static JCommander prepareParser(CliOptions options) {
        JCommander.Builder builder = JCommander.newBuilder().programName(SchedulerMain.class.getName());

        builder.addConverterFactory(new IStringConverterFactory() {
            private Map<Class<?>, Class<? extends IStringConverter<?>>> classConverters = ImmutableMap
                    .<Class<?>, Class<? extends IStringConverter<?>>>builder()
                    .put(Class.class, ClassConverter.class).put(DataAmount.class, DataAmountConverter.class)
                    .put(DockerParameter.class, DockerParameterConverter.class)
                    .put(InetSocketAddress.class, InetSocketAddressConverter.class)
                    .put(KerberosPrincipal.class, KerberosPrincipalConverter.class)
                    .put(TimeAmount.class, TimeAmountConverter.class).put(Volume.class, VolumeConverter.class)
                    .build();

            @SuppressWarnings("unchecked")
            @Override
            public <T> Class<? extends IStringConverter<T>> getConverter(Class<T> forType) {
                return (Class<IStringConverter<T>>) classConverters.get(forType);
            }
        });

        builder.addObject(getOptionsObjects(options));
        return builder.build();
    }

    /**
     * Applies arg values to the options object.
     *
     * @param args Command line arguments.
     */
    @VisibleForTesting
    public static CliOptions parseOptions(String... args) {
        JCommander parser = null;
        try {
            parser = prepareParser(new CliOptions(ImmutableList.copyOf(customOptions)));

            // We first perform a 'dummy' parsing round.  This induces classloading on any third-party
            // code, where they can statically invoke registerCustomOptions().
            parser.setAcceptUnknownOptions(true);
            parser.parseWithoutValidation(args);

            CliOptions options = new CliOptions(ImmutableList.copyOf(customOptions));
            parser = prepareParser(options);
            parser.parse(args);

            LOG.info("-----------------------------------------------------------------------");
            LOG.info("Parameters:");
            parser.getParameters().stream()
                    .map(param -> param.getLongestName() + ": " + param.getParameterized().get(param.getObject()))
                    .sorted().forEach(LOG::info);
            LOG.info("-----------------------------------------------------------------------");

            instance = options;
            return options;
        } catch (ParameterException e) {
            if (parser != null) {
                parser.usage();
            }
            LOG.error(e.getMessage());
            System.exit(1);
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets the static and globally-accessible CLI options.  This exists only to support legacy use
     * cases that cannot yet support injected arguments.  New callers should not be added.
     *
     * @return global options
     */
    public static CliOptions legacyGetStaticOptions() {
        if (instance == null) {
            throw new IllegalStateException("Attempted to fetch command line arguments before parsing.");
        }
        return instance;
    }

    /**
     * Registers a custom options container for inclusion during command line option parsing.  This
     * is useful to allow third-party modules to include custom command line options.
     *
     * @param options Custom options object.
     *                See {@link com.beust.jcommander.JCommander.Builder#addObject(Object)} for
     *                details.
     */
    public static void registerCustomOptions(Object options) {
        Preconditions.checkState(instance == null,
                "Attempted to register custom options after command line parsing.");

        customOptions.add(options);
    }

    @VisibleForTesting
    static List<Object> getOptionsObjects(CliOptions options) {
        ImmutableList.Builder<Object> objects = ImmutableList.builder();

        // Reflect on fields defined in CliOptions to DRY and avoid mistakes of forgetting to add an
        // option field here.
        for (Field field : CliOptions.class.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }

            try {
                if (Iterable.class.isAssignableFrom(field.getType())) {
                    Iterable<?> iterableValue = (Iterable<?>) field.get(options);
                    objects.addAll(iterableValue);
                } else {
                    objects.add(field.get(options));
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        return objects.build();
    }
}