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