org.springframework.core.env.CommandLinePropertySource Maven / Gradle / Ivy
/*
* 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}.
*
* Purpose and General Usage
*
* 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:
*
*
* public static void main(String[] args) {
* CommandLinePropertySource clps = ...;
* AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
* ctx.getEnvironment().getPropertySources().addFirst(clps);
* ctx.register(AppConfig.class);
* ctx.refresh();
* }
*
* With the bootstrap logic above, the {@code AppConfig} class may {@code @Inject} the
* Spring {@code Environment} and query it directly for properties:
*
*
* @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;
* }
* }
*
* 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.
*
* 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 } element. For example:
*
*
* @Component
* public class MyComponent {
*
* @Value("my.property:defaultVal")
* private String myProperty;
*
* public void getMyProperty() {
* return this.myProperty;
* }
*
* // ...
* }
*
* Working with option arguments
*
* 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:
*
*
--o1=v1 --o2
*
* 'o1' and 'o2' are treated as "option arguments", and the following assertions would
* evaluate true:
*
*
* 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;
*
*
* 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.
*
* 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.
*
*
Working with non-option arguments
*
* 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:
*
*
--o1=v1 --o2=v2 /path/to/file1 /path/to/file2
*
* 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:
*
*
* 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");
*
*
* 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:
*
*
* 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");
*
*
* 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":
*
*
* 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();
* }
*
* Limitations
*
* 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
* after 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 extends EnumerablePropertySource {
/** 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 nonOptionArguments = this.getNonOptionArgs();
if (nonOptionArguments.isEmpty()) {
return null;
}
else {
return StringUtils.collectionToCommaDelimitedString(nonOptionArguments);
}
}
Collection 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.
*
* - if the option is present and has no argument (e.g.: "--foo"), return an empty
* collection ({@code []})
* - if the option is present and has a single value (e.g. "--foo=bar"), return a
* collection having one element ({@code ["bar"]})
* - 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"]})
* - if the option is not present, return {@code null}
*
*/
protected abstract List getOptionValues(String name);
/**
* Return the collection of non-option arguments parsed from the command line.
* Never {@code null}.
*/
protected abstract List getNonOptionArgs();
}