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