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