001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core.env; 018 019import java.util.Collection; 020import java.util.List; 021 022import org.springframework.lang.Nullable; 023import org.springframework.util.StringUtils; 024 025/** 026 * Abstract base class for {@link PropertySource} implementations backed by command line 027 * arguments. The parameterized type {@code T} represents the underlying source of command 028 * line options. This may be as simple as a String array in the case of 029 * {@link SimpleCommandLinePropertySource}, or specific to a particular API such as JOpt's 030 * {@code OptionSet} in the case of {@link JOptCommandLinePropertySource}. 031 * 032 * <h3>Purpose and General Usage</h3> 033 * 034 * For use in standalone Spring-based applications, i.e. those that are bootstrapped via 035 * a traditional {@code main} method accepting a {@code String[]} of arguments from the 036 * command line. In many cases, processing command-line arguments directly within the 037 * {@code main} method may be sufficient, but in other cases, it may be desirable to 038 * inject arguments as values into Spring beans. It is this latter set of cases in which 039 * a {@code CommandLinePropertySource} becomes useful. A {@code CommandLinePropertySource} 040 * will typically be added to the {@link Environment} of the Spring 041 * {@code ApplicationContext}, at which point all command line arguments become available 042 * through the {@link Environment#getProperty(String)} family of methods. For example: 043 * 044 * <pre class="code"> 045 * public static void main(String[] args) { 046 * CommandLinePropertySource clps = ...; 047 * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 048 * ctx.getEnvironment().getPropertySources().addFirst(clps); 049 * ctx.register(AppConfig.class); 050 * ctx.refresh(); 051 * }</pre> 052 * 053 * With the bootstrap logic above, the {@code AppConfig} class may {@code @Inject} the 054 * Spring {@code Environment} and query it directly for properties: 055 * 056 * <pre class="code"> 057 * @Configuration 058 * public class AppConfig { 059 * 060 * @Inject Environment env; 061 * 062 * @Bean 063 * public void DataSource dataSource() { 064 * MyVendorDataSource dataSource = new MyVendorDataSource(); 065 * dataSource.setHostname(env.getProperty("db.hostname", "localhost")); 066 * dataSource.setUsername(env.getRequiredProperty("db.username")); 067 * dataSource.setPassword(env.getRequiredProperty("db.password")); 068 * // ... 069 * return dataSource; 070 * } 071 * }</pre> 072 * 073 * Because the {@code CommandLinePropertySource} was added to the {@code Environment}'s 074 * set of {@link MutablePropertySources} using the {@code #addFirst} method, it has 075 * highest search precedence, meaning that while "db.hostname" and other properties may 076 * exist in other property sources such as the system environment variables, it will be 077 * chosen from the command line property source first. This is a reasonable approach 078 * given that arguments specified on the command line are naturally more specific than 079 * those specified as environment variables. 080 * 081 * <p>As an alternative to injecting the {@code Environment}, Spring's {@code @Value} 082 * annotation may be used to inject these properties, given that a {@link 083 * PropertySourcesPropertyResolver} bean has been registered, either directly or through 084 * using the {@code <context:property-placeholder>} element. For example: 085 * 086 * <pre class="code"> 087 * @Component 088 * public class MyComponent { 089 * 090 * @Value("my.property:defaultVal") 091 * private String myProperty; 092 * 093 * public void getMyProperty() { 094 * return this.myProperty; 095 * } 096 * 097 * // ... 098 * }</pre> 099 * 100 * <h3>Working with option arguments</h3> 101 * 102 * <p>Individual command line arguments are represented as properties through the usual 103 * {@link PropertySource#getProperty(String)} and 104 * {@link PropertySource#containsProperty(String)} methods. For example, given the 105 * following command line: 106 * 107 * <pre class="code">--o1=v1 --o2</pre> 108 * 109 * 'o1' and 'o2' are treated as "option arguments", and the following assertions would 110 * evaluate true: 111 * 112 * <pre class="code"> 113 * CommandLinePropertySource<?> ps = ... 114 * assert ps.containsProperty("o1") == true; 115 * assert ps.containsProperty("o2") == true; 116 * assert ps.containsProperty("o3") == false; 117 * assert ps.getProperty("o1").equals("v1"); 118 * assert ps.getProperty("o2").equals(""); 119 * assert ps.getProperty("o3") == null; 120 * </pre> 121 * 122 * Note that the 'o2' option has no argument, but {@code getProperty("o2")} resolves to 123 * empty string ({@code ""}) as opposed to {@code null}, while {@code getProperty("o3")} 124 * resolves to {@code null} because it was not specified. This behavior is consistent with 125 * the general contract to be followed by all {@code PropertySource} implementations. 126 * 127 * <p>Note also that while "--" was used in the examples above to denote an option 128 * argument, this syntax may vary across individual command line argument libraries. For 129 * example, a JOpt- or Commons CLI-based implementation may allow for single dash ("-") 130 * "short" option arguments, etc. 131 * 132 * <h3>Working with non-option arguments</h3> 133 * 134 * <p>Non-option arguments are also supported through this abstraction. Any arguments 135 * supplied without an option-style prefix such as "-" or "--" are considered "non-option 136 * arguments" and available through the special {@linkplain 137 * #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME "nonOptionArgs"} property. If multiple 138 * non-option arguments are specified, the value of this property will be a 139 * comma-delimited string containing all of the arguments. This approach ensures a simple 140 * and consistent return type (String) for all properties from a {@code 141 * CommandLinePropertySource} and at the same time lends itself to conversion when used 142 * in conjunction with the Spring {@link Environment} and its built-in {@code 143 * ConversionService}. Consider the following example: 144 * 145 * <pre class="code">--o1=v1 --o2=v2 /path/to/file1 /path/to/file2</pre> 146 * 147 * In this example, "o1" and "o2" would be considered "option arguments", while the two 148 * filesystem paths qualify as "non-option arguments". As such, the following assertions 149 * will evaluate true: 150 * 151 * <pre class="code"> 152 * CommandLinePropertySource<?> ps = ... 153 * assert ps.containsProperty("o1") == true; 154 * assert ps.containsProperty("o2") == true; 155 * assert ps.containsProperty("nonOptionArgs") == true; 156 * assert ps.getProperty("o1").equals("v1"); 157 * assert ps.getProperty("o2").equals("v2"); 158 * assert ps.getProperty("nonOptionArgs").equals("/path/to/file1,/path/to/file2"); 159 * </pre> 160 * 161 * <p>As mentioned above, when used in conjunction with the Spring {@code Environment} 162 * abstraction, this comma-delimited string may easily be converted to a String array or 163 * list: 164 * 165 * <pre class="code"> 166 * Environment env = applicationContext.getEnvironment(); 167 * String[] nonOptionArgs = env.getProperty("nonOptionArgs", String[].class); 168 * assert nonOptionArgs[0].equals("/path/to/file1"); 169 * assert nonOptionArgs[1].equals("/path/to/file2"); 170 * </pre> 171 * 172 * <p>The name of the special "non-option arguments" property may be customized through 173 * the {@link #setNonOptionArgsPropertyName(String)} method. Doing so is recommended as 174 * it gives proper semantic value to non-option arguments. For example, if filesystem 175 * paths are being specified as non-option arguments, it is likely preferable to refer to 176 * these as something like "file.locations" than the default of "nonOptionArgs": 177 * 178 * <pre class="code"> 179 * public static void main(String[] args) { 180 * CommandLinePropertySource clps = ...; 181 * clps.setNonOptionArgsPropertyName("file.locations"); 182 * 183 * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); 184 * ctx.getEnvironment().getPropertySources().addFirst(clps); 185 * ctx.register(AppConfig.class); 186 * ctx.refresh(); 187 * }</pre> 188 * 189 * <h3>Limitations</h3> 190 * 191 * This abstraction is not intended to expose the full power of underlying command line 192 * parsing APIs such as JOpt or Commons CLI. It's intent is rather just the opposite: to 193 * provide the simplest possible abstraction for accessing command line arguments 194 * <em>after</em> they have been parsed. So the typical case will involve fully configuring 195 * the underlying command line parsing API, parsing the {@code String[]} of arguments 196 * coming into the main method, and then simply providing the parsing results to an 197 * implementation of {@code CommandLinePropertySource}. At that point, all arguments can 198 * be considered either 'option' or 'non-option' arguments and as described above can be 199 * accessed through the normal {@code PropertySource} and {@code Environment} APIs. 200 * 201 * @author Chris Beams 202 * @since 3.1 203 * @param <T> the source type 204 * @see PropertySource 205 * @see SimpleCommandLinePropertySource 206 * @see JOptCommandLinePropertySource 207 */ 208public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> { 209 210 /** The default name given to {@link CommandLinePropertySource} instances: {@value}. */ 211 public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; 212 213 /** The default name of the property representing non-option arguments: {@value}. */ 214 public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs"; 215 216 217 private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME; 218 219 220 /** 221 * Create a new {@code CommandLinePropertySource} having the default name 222 * {@value #COMMAND_LINE_PROPERTY_SOURCE_NAME} and backed by the given source object. 223 */ 224 public CommandLinePropertySource(T source) { 225 super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); 226 } 227 228 /** 229 * Create a new {@link CommandLinePropertySource} having the given name 230 * and backed by the given source object. 231 */ 232 public CommandLinePropertySource(String name, T source) { 233 super(name, source); 234 } 235 236 237 /** 238 * Specify the name of the special "non-option arguments" property. 239 * The default is {@value #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME}. 240 */ 241 public void setNonOptionArgsPropertyName(String nonOptionArgsPropertyName) { 242 this.nonOptionArgsPropertyName = nonOptionArgsPropertyName; 243 } 244 245 /** 246 * This implementation first checks to see if the name specified is the special 247 * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property}, 248 * and if so delegates to the abstract {@link #getNonOptionArgs()} method 249 * checking to see whether it returns an empty collection. Otherwise delegates to and 250 * returns the value of the abstract {@link #containsOption(String)} method. 251 */ 252 @Override 253 public final boolean containsProperty(String name) { 254 if (this.nonOptionArgsPropertyName.equals(name)) { 255 return !this.getNonOptionArgs().isEmpty(); 256 } 257 return this.containsOption(name); 258 } 259 260 /** 261 * This implementation first checks to see if the name specified is the special 262 * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property}, 263 * and if so delegates to the abstract {@link #getNonOptionArgs()} method. If so 264 * and the collection of non-option arguments is empty, this method returns {@code 265 * null}. If not empty, it returns a comma-separated String of all non-option 266 * arguments. Otherwise delegates to and returns the result of the abstract {@link 267 * #getOptionValues(String)} method. 268 */ 269 @Override 270 @Nullable 271 public final String getProperty(String name) { 272 if (this.nonOptionArgsPropertyName.equals(name)) { 273 Collection<String> nonOptionArguments = this.getNonOptionArgs(); 274 if (nonOptionArguments.isEmpty()) { 275 return null; 276 } 277 else { 278 return StringUtils.collectionToCommaDelimitedString(nonOptionArguments); 279 } 280 } 281 Collection<String> optionValues = this.getOptionValues(name); 282 if (optionValues == null) { 283 return null; 284 } 285 else { 286 return StringUtils.collectionToCommaDelimitedString(optionValues); 287 } 288 } 289 290 291 /** 292 * Return whether the set of option arguments parsed from the command line contains 293 * an option with the given name. 294 */ 295 protected abstract boolean containsOption(String name); 296 297 /** 298 * Return the collection of values associated with the command line option having the 299 * given name. 300 * <ul> 301 * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty 302 * collection ({@code []})</li> 303 * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a 304 * collection having one element ({@code ["bar"]})</li> 305 * <li>if the option is present and the underlying command line parsing library 306 * supports multiple arguments (e.g. "--foo=bar --foo=baz"), return a collection 307 * having elements for each value ({@code ["bar", "baz"]})</li> 308 * <li>if the option is not present, return {@code null}</li> 309 * </ul> 310 */ 311 @Nullable 312 protected abstract List<String> getOptionValues(String name); 313 314 /** 315 * Return the collection of non-option arguments parsed from the command line. 316 * Never {@code null}. 317 */ 318 protected abstract List<String> getNonOptionArgs(); 319 320}