/* * Copyright 2002-2016 the original author or authors. * * 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.springframework.core.env; import java.util.Collection; import java.util.List; import org.springframework.util.StringUtils; /** * Abstract base class for {@link PropertySource} implementations backed by command line * arguments. The parameterized type {@code T} represents the underlying source of command * line options. This may be as simple as a String array in the case of * {@link SimpleCommandLinePropertySource}, or specific to a particular API such as JOpt's * {@code OptionSet} in the case of {@link JOptCommandLinePropertySource}. * * <h3>Purpose and General Usage</h3> * * For use in standalone Spring-based applications, i.e. those that are bootstrapped via * a traditional {@code main} method accepting a {@code String[]} of arguments from the * command line. In many cases, processing command-line arguments directly within the * {@code main} method may be sufficient, but in other cases, it may be desirable to * inject arguments as values into Spring beans. It is this latter set of cases in which * a {@code CommandLinePropertySource} becomes useful. A {@code CommandLinePropertySource} * will typically be added to the {@link Environment} of the Spring * {@code ApplicationContext}, at which point all command line arguments become available * through the {@link Environment#getProperty(String)} family of methods. For example: * * <pre class="code"> * public static void main(String[] args) { * CommandLinePropertySource clps = ...; * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); * ctx.getEnvironment().getPropertySources().addFirst(clps); * ctx.register(AppConfig.class); * ctx.refresh(); * }</pre> * * With the bootstrap logic above, the {@code AppConfig} class may {@code @Inject} the * Spring {@code Environment} and query it directly for properties: * * <pre class="code"> * @Configuration * public class AppConfig { * * @Inject Environment env; * * @Bean * public void DataSource dataSource() { * MyVendorDataSource dataSource = new MyVendorDataSource(); * dataSource.setHostname(env.getProperty("db.hostname", "localhost")); * dataSource.setUsername(env.getRequiredProperty("db.username")); * dataSource.setPassword(env.getRequiredProperty("db.password")); * // ... * return dataSource; * } * }</pre> * * Because the {@code CommandLinePropertySource} was added to the {@code Environment}'s * set of {@link MutablePropertySources} using the {@code #addFirst} method, it has * highest search precedence, meaning that while "db.hostname" and other properties may * exist in other property sources such as the system environment variables, it will be * chosen from the command line property source first. This is a reasonable approach * given that arguments specified on the command line are naturally more specific than * those specified as environment variables. * * <p>As an alternative to injecting the {@code Environment}, Spring's {@code @Value} * annotation may be used to inject these properties, given that a {@link * PropertySourcesPropertyResolver} bean has been registered, either directly or through * using the {@code <context:property-placeholder>} element. For example: * * <pre class="code"> * @Component * public class MyComponent { * * @Value("my.property:defaultVal") * private String myProperty; * * public void getMyProperty() { * return this.myProperty; * } * * // ... * }</pre> * * <h3>Working with option arguments</h3> * * <p>Individual command line arguments are represented as properties through the usual * {@link PropertySource#getProperty(String)} and * {@link PropertySource#containsProperty(String)} methods. For example, given the * following command line: * * <pre class="code">--o1=v1 --o2</pre> * * 'o1' and 'o2' are treated as "option arguments", and the following assertions would * evaluate true: * * <pre class="code"> * CommandLinePropertySource<?> ps = ... * assert ps.containsProperty("o1") == true; * assert ps.containsProperty("o2") == true; * assert ps.containsProperty("o3") == false; * assert ps.getProperty("o1").equals("v1"); * assert ps.getProperty("o2").equals(""); * assert ps.getProperty("o3") == null; * </pre> * * Note that the 'o2' option has no argument, but {@code getProperty("o2")} resolves to * empty string ({@code ""}) as opposed to {@code null}, while {@code getProperty("o3")} * resolves to {@code null} because it was not specified. This behavior is consistent with * the general contract to be followed by all {@code PropertySource} implementations. * * <p>Note also that while "--" was used in the examples above to denote an option * argument, this syntax may vary across individual command line argument libraries. For * example, a JOpt- or Commons CLI-based implementation may allow for single dash ("-") * "short" option arguments, etc. * * <h3>Working with non-option arguments</h3> * * <p>Non-option arguments are also supported through this abstraction. Any arguments * supplied without an option-style prefix such as "-" or "--" are considered "non-option * arguments" and available through the special {@linkplain * #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME "nonOptionArgs"} property. If multiple * non-option arguments are specified, the value of this property will be a * comma-delimited string containing all of the arguments. This approach ensures a simple * and consistent return type (String) for all properties from a {@code * CommandLinePropertySource} and at the same time lends itself to conversion when used * in conjunction with the Spring {@link Environment} and its built-in {@code * ConversionService}. Consider the following example: * * <pre class="code">--o1=v1 --o2=v2 /path/to/file1 /path/to/file2</pre> * * In this example, "o1" and "o2" would be considered "option arguments", while the two * filesystem paths qualify as "non-option arguments". As such, the following assertions * will evaluate true: * * <pre class="code"> * CommandLinePropertySource<?> ps = ... * assert ps.containsProperty("o1") == true; * assert ps.containsProperty("o2") == true; * assert ps.containsProperty("nonOptionArgs") == true; * assert ps.getProperty("o1").equals("v1"); * assert ps.getProperty("o2").equals("v2"); * assert ps.getProperty("nonOptionArgs").equals("/path/to/file1,/path/to/file2"); * </pre> * * <p>As mentioned above, when used in conjunction with the Spring {@code Environment} * abstraction, this comma-delimited string may easily be converted to a String array or * list: * * <pre class="code"> * Environment env = applicationContext.getEnvironment(); * String[] nonOptionArgs = env.getProperty("nonOptionArgs", String[].class); * assert nonOptionArgs[0].equals("/path/to/file1"); * assert nonOptionArgs[1].equals("/path/to/file2"); * </pre> * * <p>The name of the special "non-option arguments" property may be customized through * the {@link #setNonOptionArgsPropertyName(String)} method. Doing so is recommended as * it gives proper semantic value to non-option arguments. For example, if filesystem * paths are being specified as non-option arguments, it is likely preferable to refer to * these as something like "file.locations" than the default of "nonOptionArgs": * * <pre class="code"> * public static void main(String[] args) { * CommandLinePropertySource clps = ...; * clps.setNonOptionArgsPropertyName("file.locations"); * * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); * ctx.getEnvironment().getPropertySources().addFirst(clps); * ctx.register(AppConfig.class); * ctx.refresh(); * }</pre> * * <h3>Limitations</h3> * * This abstraction is not intended to expose the full power of underlying command line * parsing APIs such as JOpt or Commons CLI. It's intent is rather just the opposite: to * provide the simplest possible abstraction for accessing command line arguments * <em>after</em> they have been parsed. So the typical case will involve fully configuring * the underlying command line parsing API, parsing the {@code String[]} of arguments * coming into the main method, and then simply providing the parsing results to an * implementation of {@code CommandLinePropertySource}. At that point, all arguments can * be considered either 'option' or 'non-option' arguments and as described above can be * accessed through the normal {@code PropertySource} and {@code Environment} APIs. * * @author Chris Beams * @since 3.1 * @see PropertySource * @see SimpleCommandLinePropertySource * @see JOptCommandLinePropertySource */ public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> { /** The default name given to {@link CommandLinePropertySource} instances: {@value} */ public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; /** The default name of the property representing non-option arguments: {@value} */ public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs"; private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME; /** * Create a new {@code CommandLinePropertySource} having the default name * {@value #COMMAND_LINE_PROPERTY_SOURCE_NAME} and backed by the given source object. */ public CommandLinePropertySource(T source) { super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); } /** * Create a new {@link CommandLinePropertySource} having the given name * and backed by the given source object. */ public CommandLinePropertySource(String name, T source) { super(name, source); } /** * Specify the name of the special "non-option arguments" property. * The default is {@value #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME}. */ public void setNonOptionArgsPropertyName(String nonOptionArgsPropertyName) { this.nonOptionArgsPropertyName = nonOptionArgsPropertyName; } /** * This implementation first checks to see if the name specified is the special * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property}, * and if so delegates to the abstract {@link #getNonOptionArgs()} method * checking to see whether it returns an empty collection. Otherwise delegates to and * returns the value of the abstract {@link #containsOption(String)} method. */ @Override public final boolean containsProperty(String name) { if (this.nonOptionArgsPropertyName.equals(name)) { return !this.getNonOptionArgs().isEmpty(); } return this.containsOption(name); } /** * This implementation first checks to see if the name specified is the special * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property}, * and if so delegates to the abstract {@link #getNonOptionArgs()} method. If so * and the collection of non-option arguments is empty, this method returns {@code * null}. If not empty, it returns a comma-separated String of all non-option * arguments. Otherwise delegates to and returns the result of the abstract {@link * #getOptionValues(String)} method. */ @Override public final String getProperty(String name) { if (this.nonOptionArgsPropertyName.equals(name)) { Collection<String> nonOptionArguments = this.getNonOptionArgs(); if (nonOptionArguments.isEmpty()) { return null; } else { return StringUtils.collectionToCommaDelimitedString(nonOptionArguments); } } Collection<String> optionValues = this.getOptionValues(name); if (optionValues == null) { return null; } else { return StringUtils.collectionToCommaDelimitedString(optionValues); } } /** * Return whether the set of option arguments parsed from the command line contains * an option with the given name. */ protected abstract boolean containsOption(String name); /** * Return the collection of values associated with the command line option having the * given name. * <ul> * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty * collection ({@code []})</li> * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a * collection having one element ({@code ["bar"]})</li> * <li>if the option is present and the underlying command line parsing library * supports multiple arguments (e.g. "--foo=bar --foo=baz"), return a collection * having elements for each value ({@code ["bar", "baz"]})</li> * <li>if the option is not present, return {@code null}</li> * </ul> */ protected abstract List<String> getOptionValues(String name); /** * Return the collection of non-option arguments parsed from the command line. * Never {@code null}. */ protected abstract List<String> getNonOptionArgs(); }