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 * &#064;Configuration
058 * public class AppConfig {
059 *
060 *     &#064;Inject Environment env;
061 *
062 *     &#064;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 * &#064;Component
088 * public class MyComponent {
089 *
090 *     &#064;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}