1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tradefed.config;
18 
19 import com.android.tradefed.command.CommandScheduler;
20 import com.android.tradefed.command.ICommandScheduler;
21 import com.android.tradefed.config.gcs.GCSConfigurationFactory;
22 import com.android.tradefed.device.DeviceManager;
23 import com.android.tradefed.device.DeviceSelectionOptions;
24 import com.android.tradefed.device.IDeviceManager;
25 import com.android.tradefed.device.IDeviceMonitor;
26 import com.android.tradefed.device.IDeviceSelection;
27 import com.android.tradefed.device.IMultiDeviceRecovery;
28 import com.android.tradefed.host.HostOptions;
29 import com.android.tradefed.host.IHostOptions;
30 import com.android.tradefed.invoker.shard.IShardHelper;
31 import com.android.tradefed.invoker.shard.StrictShardHelper;
32 import com.android.tradefed.log.ITerribleFailureHandler;
33 import com.android.tradefed.util.ArrayUtil;
34 import com.android.tradefed.util.MultiMap;
35 import com.android.tradefed.util.hostmetric.IHostMonitor;
36 import com.android.tradefed.util.keystore.IKeyStoreFactory;
37 import com.android.tradefed.util.keystore.StubKeyStoreFactory;
38 
39 import com.google.common.annotations.VisibleForTesting;
40 
41 import org.kxml2.io.KXmlSerializer;
42 
43 import java.io.File;
44 import java.io.IOException;
45 import java.io.PrintStream;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collection;
49 import java.util.HashMap;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Map;
53 
54 /**
55  * An {@link IGlobalConfiguration} implementation that stores the loaded config objects in a map
56  */
57 public class GlobalConfiguration implements IGlobalConfiguration {
58     // type names for built in configuration objects
59     public static final String DEVICE_MONITOR_TYPE_NAME = "device_monitor";
60     public static final String HOST_MONITOR_TYPE_NAME = "host_monitor";
61     public static final String DEVICE_MANAGER_TYPE_NAME = "device_manager";
62     public static final String WTF_HANDLER_TYPE_NAME = "wtf_handler";
63     public static final String HOST_OPTIONS_TYPE_NAME = "host_options";
64     public static final String DEVICE_REQUIREMENTS_TYPE_NAME = "device_requirements";
65     public static final String SCHEDULER_TYPE_NAME = "command_scheduler";
66     public static final String MULTI_DEVICE_RECOVERY_TYPE_NAME = "multi_device_recovery";
67     public static final String KEY_STORE_TYPE_NAME = "key_store";
68     public static final String SHARDING_STRATEGY_TYPE_NAME = "sharding_strategy";
69     public static final String GLOBAL_CONFIG_SERVER = "global_config_server";
70 
71     public static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
72     public static final String GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE =
73             "TF_GLOBAL_CONFIG_SERVER_CONFIG";
74     private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
75 
76     private static Map<String, ObjTypeInfo> sObjTypeMap = null;
77     private static IGlobalConfiguration sInstance = null;
78     private static final Object sInstanceLock = new Object();
79 
80     // Empty embedded configuration available by default
81     private static final String DEFAULT_EMPTY_CONFIG_NAME = "empty";
82 
83     // Configurations to be passed to subprocess
84     private static final String[] CONFIGS_FOR_SUBPROCESS_WHITE_LIST =
85             new String[] {KEY_STORE_TYPE_NAME};
86 
87     /** Mapping of config object type name to config objects. */
88     private Map<String, List<Object>> mConfigMap;
89     private MultiMap<String, String> mOptionMap;
90     private final String mName;
91     private final String mDescription;
92 
93     /**
94      * Returns a reference to the singleton {@link GlobalConfiguration} instance for this TF
95      * instance.
96      *
97      * @throws IllegalStateException if {@link #createGlobalConfiguration(String[])} has not
98      *         already been called.
99      */
getInstance()100     public static IGlobalConfiguration getInstance() {
101         if (sInstance == null) {
102             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
103         }
104         return sInstance;
105     }
106 
107     /**
108      * Returns a reference to the singleton {@link DeviceManager} instance for this TF
109      * instance.
110      *
111      * @throws IllegalStateException if {@link #createGlobalConfiguration(String[])} has not
112      *         already been called.
113      */
getDeviceManagerInstance()114     public static IDeviceManager getDeviceManagerInstance() {
115         if (sInstance == null) {
116             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
117         }
118         return sInstance.getDeviceManager();
119     }
120 
getHostMonitorInstances()121     public static List<IHostMonitor> getHostMonitorInstances() {
122         if (sInstance == null) {
123             throw new IllegalStateException("GlobalConfiguration has not yet been initialized!");
124         }
125         return sInstance.getHostMonitors();
126     }
127 
128     /**
129      * Sets up the {@link GlobalConfiguration} singleton for this TF instance.  Must be called
130      * once and only once, before anything attempts to call {@link #getInstance()}
131      *
132      * @throws IllegalStateException if called more than once
133      */
createGlobalConfiguration(String[] args)134     public static List<String> createGlobalConfiguration(String[] args)
135             throws ConfigurationException {
136         synchronized (sInstanceLock) {
137             if (sInstance != null) {
138                 throw new IllegalStateException("GlobalConfiguration is already initialized!");
139             }
140             List<String> nonGlobalArgs = new ArrayList<String>(args.length);
141             List<String> nonConfigServerArgs = new ArrayList<String>(args.length);
142             IConfigurationServer globalConfigServer =
143                     createGlobalConfigServer(args, nonConfigServerArgs);
144             if (globalConfigServer == null) {
145                 String path = getGlobalConfigPath();
146                 IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
147                 sInstance =
148                         configFactory.createGlobalConfigurationFromArgs(
149                                 ArrayUtil.buildArray(new String[] {path}, args), nonGlobalArgs);
150             } else {
151                 String currentHostConfig = globalConfigServer.getCurrentHostConfig();
152                 IConfigurationFactory configFactory =
153                         GCSConfigurationFactory.getInstance(globalConfigServer);
154                 sInstance =
155                         configFactory.createGlobalConfigurationFromArgs(
156                                 ArrayUtil.buildArray(
157                                         new String[] {currentHostConfig},
158                                         nonConfigServerArgs.toArray(new String[0])),
159                                 nonGlobalArgs);
160             }
161             // Validate that madatory options have been set
162             sInstance.validateOptions();
163             return nonGlobalArgs;
164         }
165     }
166 
167     /**
168      * Returns the path to a global config, if one exists, or <code>null</code> if none could be
169      * found.
170      * <p />
171      * Search locations, in decreasing order of precedence
172      * <ol>
173      *   <li><code>$TF_GLOBAL_CONFIG</code> environment variable</li>
174      *   <li><code>tf_global_config.xml</code> file in $PWD</li>
175      *   <li>(FIXME) <code>tf_global_config.xml</code> file in dir where <code>tradefed.sh</code>
176      *       lives</li>
177      * </ol>
178      */
getGlobalConfigPath()179     private static String getGlobalConfigPath() {
180         String path = System.getenv(GLOBAL_CONFIG_VARIABLE);
181         if (path != null) {
182             // don't actually check for accessibility here, since the variable might be specifying
183             // a java resource rather than a filename.  Even so, this can help the user figure out
184             // which global config (if any) was picked up by TF.
185             System.out.format(
186                     "Attempting to use global config \"%s\" from variable $%s.\n",
187                     path, GLOBAL_CONFIG_VARIABLE);
188             return path;
189         }
190 
191         File file = new File(GLOBAL_CONFIG_FILENAME);
192         if (file.exists()) {
193             path = file.getPath();
194             System.out.format("Attempting to use autodetected global config \"%s\".\n", path);
195             return path;
196         }
197 
198         // FIXME: search in tradefed.sh launch dir (or classpath?)
199 
200         // Use default empty known global config
201         return DEFAULT_EMPTY_CONFIG_NAME;
202     }
203 
204     /**
205      * Returns an {@link IConfigurationServer}, if one exists, or <code>null</code> if none could be
206      * found.
207      *
208      * @param args for config server
209      * @param nonConfigServerArgs a list which will be populated with the arguments that weren't
210      *     processed as global arguments
211      * @return an {@link IConfigurationServer}
212      * @throws ConfigurationException
213      */
214     @VisibleForTesting
createGlobalConfigServer( String[] args, List<String> nonConfigServerArgs)215     static IConfigurationServer createGlobalConfigServer(
216             String[] args, List<String> nonConfigServerArgs) throws ConfigurationException {
217         String path = System.getenv(GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE);
218         if (path == null) {
219             // No config server, should use config files.
220             nonConfigServerArgs.addAll(Arrays.asList(args));
221             return null;
222         } else {
223             System.out.format("Use global config server config %s.\n", path);
224         }
225         IConfigurationServer configServer = null;
226         IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
227         IGlobalConfiguration configServerConfig =
228                 configFactory.createGlobalConfigurationFromArgs(
229                         ArrayUtil.buildArray(new String[] {path}, args), nonConfigServerArgs);
230         configServer = configServerConfig.getGlobalConfigServer();
231         return configServer;
232     }
233 
234     /**
235      * Container struct for built-in config object type
236      */
237     private static class ObjTypeInfo {
238         final Class<?> mExpectedType;
239         /** true if a list (ie many objects in a single config) are supported for this type */
240         final boolean mIsListSupported;
241 
ObjTypeInfo(Class<?> expectedType, boolean isList)242         ObjTypeInfo(Class<?> expectedType, boolean isList) {
243             mExpectedType = expectedType;
244             mIsListSupported = isList;
245         }
246     }
247 
248     /**
249      * Determine if given config object type name is a built in object
250      *
251      * @param typeName the config object type name
252      * @return <code>true</code> if name is a built in object type
253      */
isBuiltInObjType(String typeName)254     static boolean isBuiltInObjType(String typeName) {
255         return getObjTypeMap().containsKey(typeName);
256     }
257 
getObjTypeMap()258     private static synchronized Map<String, ObjTypeInfo> getObjTypeMap() {
259         if (sObjTypeMap == null) {
260             sObjTypeMap = new HashMap<String, ObjTypeInfo>();
261             sObjTypeMap.put(HOST_OPTIONS_TYPE_NAME, new ObjTypeInfo(IHostOptions.class, false));
262             sObjTypeMap.put(DEVICE_MONITOR_TYPE_NAME, new ObjTypeInfo(IDeviceMonitor.class, true));
263             sObjTypeMap.put(HOST_MONITOR_TYPE_NAME, new ObjTypeInfo(IHostMonitor.class, true));
264             sObjTypeMap.put(DEVICE_MANAGER_TYPE_NAME, new ObjTypeInfo(IDeviceManager.class, false));
265             sObjTypeMap.put(DEVICE_REQUIREMENTS_TYPE_NAME, new ObjTypeInfo(IDeviceSelection.class,
266                     false));
267             sObjTypeMap.put(WTF_HANDLER_TYPE_NAME,
268                     new ObjTypeInfo(ITerribleFailureHandler.class, false));
269             sObjTypeMap.put(SCHEDULER_TYPE_NAME, new ObjTypeInfo(ICommandScheduler.class, false));
270             sObjTypeMap.put(
271                     MULTI_DEVICE_RECOVERY_TYPE_NAME,
272                     new ObjTypeInfo(IMultiDeviceRecovery.class, true));
273             sObjTypeMap.put(KEY_STORE_TYPE_NAME, new ObjTypeInfo(IKeyStoreFactory.class, false));
274             sObjTypeMap.put(
275                     SHARDING_STRATEGY_TYPE_NAME, new ObjTypeInfo(IShardHelper.class, false));
276             sObjTypeMap.put(
277                     GLOBAL_CONFIG_SERVER, new ObjTypeInfo(IConfigurationServer.class, false));
278         }
279         return sObjTypeMap;
280     }
281 
282     /**
283      * Creates a {@link GlobalConfiguration} with default config objects
284      */
GlobalConfiguration(String name, String description)285     GlobalConfiguration(String name, String description) {
286         mName = name;
287         mDescription = description;
288         mConfigMap = new LinkedHashMap<String, List<Object>>();
289         mOptionMap = new MultiMap<String, String>();
290         setHostOptions(new HostOptions());
291         setDeviceRequirements(new DeviceSelectionOptions());
292         setDeviceManager(new DeviceManager());
293         setCommandScheduler(new CommandScheduler());
294         setKeyStoreFactory(new StubKeyStoreFactory());
295         setShardingStrategy(new StrictShardHelper());
296     }
297 
298     /**
299      * @return the name of this {@link Configuration}
300      */
getName()301     public String getName() {
302         return mName;
303     }
304 
305     /**
306      * @return a short user readable description this {@link Configuration}
307      */
getDescription()308     public String getDescription() {
309         return mDescription;
310     }
311 
312     /**
313      * {@inheritDoc}
314      */
315     @Override
getHostOptions()316     public IHostOptions getHostOptions() {
317         return (IHostOptions) getConfigurationObject(HOST_OPTIONS_TYPE_NAME);
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     @SuppressWarnings("unchecked")
getDeviceMonitors()323     public List<IDeviceMonitor> getDeviceMonitors() {
324         return (List<IDeviceMonitor>) getConfigurationObjectList(DEVICE_MONITOR_TYPE_NAME);
325     }
326 
327     @Override
getGlobalConfigServer()328     public IConfigurationServer getGlobalConfigServer() {
329         return (IConfigurationServer) getConfigurationObject(GLOBAL_CONFIG_SERVER);
330     }
331 
332     /**
333      * {@inheritDoc}
334      */
335     @Override
336     @SuppressWarnings("unchecked")
getHostMonitors()337     public List<IHostMonitor> getHostMonitors() {
338         return (List<IHostMonitor>) getConfigurationObjectList(HOST_MONITOR_TYPE_NAME);
339     }
340 
341     /**
342      * {@inheritDoc}
343      */
344     @Override
getWtfHandler()345     public ITerribleFailureHandler getWtfHandler() {
346         return (ITerribleFailureHandler) getConfigurationObject(WTF_HANDLER_TYPE_NAME);
347     }
348 
349     /**
350      * {@inheritDoc}
351      */
352     @Override
getKeyStoreFactory()353     public IKeyStoreFactory getKeyStoreFactory() {
354         return (IKeyStoreFactory) getConfigurationObject(KEY_STORE_TYPE_NAME);
355     }
356 
357     /** {@inheritDoc} */
358     @Override
getShardingStrategy()359     public IShardHelper getShardingStrategy() {
360         return (IShardHelper) getConfigurationObject(SHARDING_STRATEGY_TYPE_NAME);
361     }
362 
363     /** {@inheritDoc} */
364     @Override
getDeviceManager()365     public IDeviceManager getDeviceManager() {
366         return (IDeviceManager)getConfigurationObject(DEVICE_MANAGER_TYPE_NAME);
367     }
368 
369     /**
370      * {@inheritDoc}
371      */
372     @Override
getDeviceRequirements()373     public IDeviceSelection getDeviceRequirements() {
374         return (IDeviceSelection)getConfigurationObject(DEVICE_REQUIREMENTS_TYPE_NAME);
375     }
376 
377     /**
378      * {@inheritDoc}
379      */
380     @Override
getCommandScheduler()381     public ICommandScheduler getCommandScheduler() {
382         return (ICommandScheduler)getConfigurationObject(SCHEDULER_TYPE_NAME);
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388     @Override
389     @SuppressWarnings("unchecked")
getMultiDeviceRecoveryHandlers()390     public List<IMultiDeviceRecovery> getMultiDeviceRecoveryHandlers() {
391         return (List<IMultiDeviceRecovery>)getConfigurationObjectList(
392                 MULTI_DEVICE_RECOVERY_TYPE_NAME);
393     }
394 
395     /**
396      * Internal helper to get the list of config object
397      */
getConfigurationObjectList(String typeName)398     private List<?> getConfigurationObjectList(String typeName) {
399         return mConfigMap.get(typeName);
400     }
401 
402     /**
403      * {@inheritDoc}
404      */
405     @Override
getConfigurationObject(String typeName)406     public Object getConfigurationObject(String typeName) {
407         List<?> configObjects = getConfigurationObjectList(typeName);
408         if (configObjects == null) {
409             return null;
410         }
411         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
412         if (typeInfo != null && typeInfo.mIsListSupported) {
413             throw new IllegalStateException(
414                     String.format(
415                             "Wrong method call for type %s. Used getConfigurationObject() for a "
416                                     + "config object that is stored as a list",
417                             typeName));
418         }
419         if (configObjects.size() != 1) {
420             throw new IllegalStateException(String.format(
421                     "Attempted to retrieve single object for %s, but %d are present",
422                     typeName, configObjects.size()));
423         }
424         return configObjects.get(0);
425     }
426 
427     /**
428      * Return a copy of all config objects
429      */
getAllConfigurationObjects()430     private Collection<Object> getAllConfigurationObjects() {
431         Collection<Object> objectsCopy = new ArrayList<Object>();
432         for (List<Object> objectList : mConfigMap.values()) {
433             objectsCopy.addAll(objectList);
434         }
435         return objectsCopy;
436     }
437 
438     /**
439      * {@inheritDoc}
440      */
441     @Override
injectOptionValue(String optionName, String optionValue)442     public void injectOptionValue(String optionName, String optionValue)
443             throws ConfigurationException {
444         injectOptionValue(optionName, null, optionValue);
445     }
446 
447     /**
448      * {@inheritDoc}
449      */
450     @Override
injectOptionValue(String optionName, String optionKey, String optionValue)451     public void injectOptionValue(String optionName, String optionKey, String optionValue)
452             throws ConfigurationException {
453         OptionSetter optionSetter = new OptionSetter(getAllConfigurationObjects());
454         optionSetter.setOptionValue(optionName, optionKey, optionValue);
455 
456         if (optionKey != null) {
457             mOptionMap.put(optionName, optionKey + "=" + optionValue);
458         } else {
459             mOptionMap.put(optionName, optionValue);
460         }
461     }
462 
463     /**
464      * {@inheritDoc}
465      */
466     @Override
getOptionValues(String optionName)467     public List<String> getOptionValues(String optionName) {
468         return mOptionMap.get(optionName);
469     }
470 
471     /**
472      * {@inheritDoc}
473      */
474     @Override
setHostOptions(IHostOptions hostOptions)475     public void setHostOptions(IHostOptions hostOptions) {
476         setConfigurationObjectNoThrow(HOST_OPTIONS_TYPE_NAME, hostOptions);
477     }
478 
479     /**
480      * {@inheritDoc}
481      */
482     @Override
setDeviceMonitor(IDeviceMonitor monitor)483     public void setDeviceMonitor(IDeviceMonitor monitor) {
484         setConfigurationObjectNoThrow(DEVICE_MONITOR_TYPE_NAME, monitor);
485     }
486 
487     /** {@inheritDoc} */
488     @Override
setHostMonitors(List<IHostMonitor> hostMonitors)489     public void setHostMonitors(List<IHostMonitor> hostMonitors) {
490         setConfigurationObjectListNoThrow(HOST_MONITOR_TYPE_NAME, hostMonitors);
491     }
492 
493     /**
494      * {@inheritDoc}
495      */
496     @Override
setWtfHandler(ITerribleFailureHandler wtfHandler)497     public void setWtfHandler(ITerribleFailureHandler wtfHandler) {
498         setConfigurationObjectNoThrow(WTF_HANDLER_TYPE_NAME, wtfHandler);
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
setKeyStoreFactory(IKeyStoreFactory factory)505     public void setKeyStoreFactory(IKeyStoreFactory factory) {
506         setConfigurationObjectNoThrow(KEY_STORE_TYPE_NAME, factory);
507     }
508 
509     /** {@inheritDoc} */
510     @Override
setShardingStrategy(IShardHelper sharding)511     public void setShardingStrategy(IShardHelper sharding) {
512         setConfigurationObjectNoThrow(SHARDING_STRATEGY_TYPE_NAME, sharding);
513     }
514 
515     /** {@inheritDoc} */
516     @Override
setDeviceManager(IDeviceManager manager)517     public void setDeviceManager(IDeviceManager manager) {
518         setConfigurationObjectNoThrow(DEVICE_MANAGER_TYPE_NAME, manager);
519     }
520 
521     /**
522      * {@inheritDoc}
523      */
524     @Override
setDeviceRequirements(IDeviceSelection devRequirements)525     public void setDeviceRequirements(IDeviceSelection devRequirements) {
526         setConfigurationObjectNoThrow(DEVICE_REQUIREMENTS_TYPE_NAME, devRequirements);
527     }
528 
529     /**
530      * {@inheritDoc}
531      */
532     @Override
setCommandScheduler(ICommandScheduler scheduler)533     public void setCommandScheduler(ICommandScheduler scheduler) {
534         setConfigurationObjectNoThrow(SCHEDULER_TYPE_NAME, scheduler);
535     }
536 
537     /**
538      * {@inheritDoc}
539      */
540     @Override
setConfigurationObject(String typeName, Object configObject)541     public void setConfigurationObject(String typeName, Object configObject)
542             throws ConfigurationException {
543         if (configObject == null) {
544             throw new IllegalArgumentException("configObject cannot be null");
545         }
546         mConfigMap.remove(typeName);
547         addObject(typeName, configObject);
548     }
549 
550     /**
551      * {@inheritDoc}
552      */
553     @Override
setConfigurationObjectList(String typeName, List<?> configList)554     public void setConfigurationObjectList(String typeName, List<?> configList)
555             throws ConfigurationException {
556         if (configList == null) {
557             throw new IllegalArgumentException("configList cannot be null");
558         }
559         mConfigMap.remove(typeName);
560         for (Object configObject : configList) {
561             addObject(typeName, configObject);
562         }
563     }
564 
565     /**
566      * A wrapper around {@link #setConfigurationObjectList(String, List)} that will not throw {@link
567      * ConfigurationException}.
568      *
569      * <p>Intended to be used in cases where its guaranteed that <var>configObject</var> is the
570      * correct type
571      */
setConfigurationObjectListNoThrow(String typeName, List<?> configList)572     private void setConfigurationObjectListNoThrow(String typeName, List<?> configList) {
573         try {
574             setConfigurationObjectList(typeName, configList);
575         } catch (ConfigurationException e) {
576             // should never happen
577             throw new IllegalArgumentException(e);
578         }
579     }
580 
581     /**
582      * Adds a loaded object to this configuration.
583      *
584      * @param typeName the unique object type name of the configuration object
585      * @param configObject the configuration object
586      * @throws ConfigurationException if object was not the correct type
587      */
addObject(String typeName, Object configObject)588     private void addObject(String typeName, Object configObject) throws ConfigurationException {
589         List<Object> objList = mConfigMap.get(typeName);
590         if (objList == null) {
591             objList = new ArrayList<Object>(1);
592             mConfigMap.put(typeName, objList);
593         }
594         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
595         if (typeInfo != null && !typeInfo.mExpectedType.isInstance(configObject)) {
596             throw new ConfigurationException(String.format(
597                     "The config object %s is not the correct type. Expected %s, received %s",
598                     typeName, typeInfo.mExpectedType.getCanonicalName(),
599                     configObject.getClass().getCanonicalName()));
600         }
601         if (typeInfo != null && !typeInfo.mIsListSupported && objList.size() > 0) {
602             throw new ConfigurationException(String.format(
603                     "Only one config object allowed for %s, but multiple were specified.",
604                     typeName));
605         }
606         objList.add(configObject);
607     }
608 
609     /**
610      * A wrapper around {@link #setConfigurationObject(String, Object)} that will not throw
611      * {@link ConfigurationException}.
612      * <p/>
613      * Intended to be used in cases where its guaranteed that <var>configObject</var> is the
614      * correct type.
615      *
616      * @param typeName
617      * @param configObject
618      */
setConfigurationObjectNoThrow(String typeName, Object configObject)619     private void setConfigurationObjectNoThrow(String typeName, Object configObject) {
620         try {
621             setConfigurationObject(typeName, configObject);
622         } catch (ConfigurationException e) {
623             // should never happen
624             throw new IllegalArgumentException(e);
625         }
626     }
627 
628     /**
629      * {@inheritDoc}
630      */
631     @Override
setOptionsFromCommandLineArgs(List<String> listArgs)632     public List<String> setOptionsFromCommandLineArgs(List<String> listArgs)
633             throws ConfigurationException {
634         ArgsOptionParser parser = new ArgsOptionParser(getAllConfigurationObjects());
635         return parser.parse(listArgs);
636     }
637 
638     /**
639      * Outputs a command line usage help text for this configuration to given printStream.
640      *
641      * @param out the {@link PrintStream} to use.
642      * @throws ConfigurationException
643      */
printCommandUsage(boolean importantOnly, PrintStream out)644     public void printCommandUsage(boolean importantOnly, PrintStream out)
645             throws ConfigurationException {
646         out.println(String.format("'%s' configuration: %s", getName(), getDescription()));
647         out.println();
648         if (importantOnly) {
649             out.println("Printing help for only the important options. " +
650                     "To see help for all options, use the --help-all flag");
651             out.println();
652         }
653         for (Map.Entry<String, List<Object>> configObjectsEntry : mConfigMap.entrySet()) {
654             for (Object configObject : configObjectsEntry.getValue()) {
655                 String optionHelp = printOptionsForObject(importantOnly,
656                         configObjectsEntry.getKey(), configObject);
657                 // only print help for object if optionHelp is non zero length
658                 if (optionHelp.length() > 0) {
659                     String classAlias = "";
660                     if (configObject.getClass().isAnnotationPresent(OptionClass.class)) {
661                         final OptionClass classAnnotation = configObject.getClass().getAnnotation(
662                                 OptionClass.class);
663                         classAlias = String.format("'%s' ", classAnnotation.alias());
664                     }
665                     out.printf("  %s%s options:", classAlias, configObjectsEntry.getKey());
666                     out.println();
667                     out.print(optionHelp);
668                     out.println();
669                 }
670             }
671         }
672     }
673 
674     /**
675      * Prints out the available config options for given configuration object.
676      *
677      * @param importantOnly print only the important options
678      * @param objectTypeName the config object type name. Used to generate more descriptive error
679      *            messages
680      * @param configObject the config object
681      * @return a {@link String} of option help text
682      * @throws ConfigurationException
683      */
printOptionsForObject(boolean importantOnly, String objectTypeName, Object configObject)684     private String printOptionsForObject(boolean importantOnly, String objectTypeName,
685             Object configObject) throws ConfigurationException {
686         return ArgsOptionParser.getOptionHelp(importantOnly, configObject);
687     }
688 
689     /**
690      * {@inheritDoc}
691      */
692     @Override
validateOptions()693     public void validateOptions() throws ConfigurationException {
694         new ArgsOptionParser(getAllConfigurationObjects()).validateMandatoryOptions();
695     }
696 
697     /** {@inheritDoc} */
698     @Override
cloneConfigWithFilter(File outputXml, String[] whitelistConfigs)699     public void cloneConfigWithFilter(File outputXml, String[] whitelistConfigs)
700             throws IOException {
701         KXmlSerializer serializer = ConfigurationUtil.createSerializer(outputXml);
702         serializer.startTag(null, ConfigurationUtil.CONFIGURATION_NAME);
703         if (whitelistConfigs == null) {
704             whitelistConfigs = CONFIGS_FOR_SUBPROCESS_WHITE_LIST;
705         }
706         for (String config : whitelistConfigs) {
707             ConfigurationUtil.dumpClassToXml(
708                     serializer, config, getConfigurationObject(config), new ArrayList<>());
709         }
710         serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
711         serializer.endDocument();
712     }
713 }
714