1 package org.broadinstitute.hellbender.utils.config; 2 3 import com.google.common.annotations.VisibleForTesting; 4 import htsjdk.samtools.util.Log; 5 import org.aeonbits.owner.Config; 6 import org.aeonbits.owner.ConfigCache; 7 import org.aeonbits.owner.Factory; 8 import org.apache.logging.log4j.Level; 9 import org.apache.logging.log4j.LogManager; 10 import org.apache.logging.log4j.Logger; 11 import org.broadinstitute.hellbender.exceptions.GATKException; 12 import org.broadinstitute.hellbender.exceptions.UserException; 13 import org.broadinstitute.hellbender.utils.ClassUtils; 14 import org.broadinstitute.hellbender.utils.LoggingUtils; 15 import org.broadinstitute.hellbender.utils.Utils; 16 17 import java.io.OutputStream; 18 import java.lang.reflect.InvocationTargetException; 19 import java.lang.reflect.Method; 20 import java.nio.file.Files; 21 import java.nio.file.Path; 22 import java.nio.file.StandardOpenOption; 23 import java.text.SimpleDateFormat; 24 import java.util.*; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 28 /** 29 * A singleton class to act as a user interface for loading configuration files from {@link org.aeonbits.owner}. 30 * This class wraps functionality in the {@link org.aeonbits.owner} configuration utilities to be a more GATK-specific 31 * interface. 32 * Created by jonn on 7/19/17. 33 */ 34 public final class ConfigFactory { 35 36 private static final Logger logger = LogManager.getLogger(ConfigFactory.class); 37 38 //======================================= 39 // Singleton members / methods: 40 private static final ConfigFactory instance; 41 42 static { 43 instance = new ConfigFactory(); 44 } 45 46 /** 47 * @return An instance of this {@link ConfigFactory}, which can be used to create a configuration. 48 */ getInstance()49 public static ConfigFactory getInstance() { 50 return instance; 51 } 52 53 // This class is a singleton, so no public construction. ConfigFactory()54 private ConfigFactory() {} 55 56 //======================================= 57 58 /** 59 * A regex to use to look for variables in the Sources annotation 60 */ 61 private static final Pattern sourcesAnnotationPathVariablePattern = Pattern.compile("\\$\\{(.*)}"); 62 63 /** 64 * Value to set each variable for configuration file paths when the variable 65 * has not been set in either Java System properties or environment properties. 66 */ 67 @VisibleForTesting 68 static final String NO_PATH_VARIABLE_VALUE = "/dev/null"; 69 70 /** 71 * A set to keep track of the classes we've already resolved for configuration path purposes: 72 */ 73 private final Set<Class<? extends Config>> alreadyResolvedPathVariables = new HashSet<>(); 74 75 // ================================================================================================================= 76 77 /** 78 * Checks each of the given {@code filenameProperties} for if they are defined in system {@link System#getProperties()} 79 * or environment {@link System#getenv()} properties. If they are not, this method will set them in the 80 * {@link org.aeonbits.owner.ConfigFactory} to an empty file path so the {@link org.aeonbits.owner.ConfigFactory} will know to try to resolve them as 81 * variables at load-time (and not as raw paths). 82 * @param filenameProperties A {@link List} of filename properties as specified in {@link Config} {@link org.aeonbits.owner.Config.Sources} annotations to check for existence in system and environment properties. 83 */ 84 @VisibleForTesting checkFileNamePropertyExistenceAndSetConfigFactoryProperties(final List<String> filenameProperties)85 void checkFileNamePropertyExistenceAndSetConfigFactoryProperties(final List<String> filenameProperties) { 86 // Grab the system properties: 87 final Properties systemProperties = System.getProperties(); 88 89 // Grab the environment properties: 90 final Map<String, String> environmentProperties = System.getenv(); 91 92 // Make sure that if our property isn't in the system, environment, and ConfigFactory 93 // properties, that we set it to a neutral value that will not contain 94 // anything (so that the property will fall back into the next value). 95 for (final String property : filenameProperties) { 96 97 if ( environmentProperties.containsKey(property) ) { 98 logger.debug("Config path variable found in Environment Properties: " + property + "=" + environmentProperties.get(property) + " - will search for config here."); 99 } 100 else if ( systemProperties.containsKey(property) ) { 101 logger.debug("Config path variable found in System Properties: " + property + "=" + systemProperties.get(property) + " - will search for config here."); 102 } 103 else if ( org.aeonbits.owner.ConfigFactory.getProperties().containsKey(property) ) { 104 logger.debug("Config path variable found in Config Factory Properties(probably from the command-line): " + property + "=" + org.aeonbits.owner.ConfigFactory.getProperty(property) + " - will search for config here."); 105 } 106 else { 107 logger.debug("Config path variable not found: " + property + 108 " - setting value to default empty variable: " + (NO_PATH_VARIABLE_VALUE == null ? "null" : String.valueOf(NO_PATH_VARIABLE_VALUE)) ); 109 org.aeonbits.owner.ConfigFactory.setProperty(property, NO_PATH_VARIABLE_VALUE); 110 } 111 } 112 } 113 114 /** 115 * Get a list of the config file variables from the given {@link Config} classes. 116 * @param configurationClasses A list of configuration classes from which to extract variable names in their {@link org.aeonbits.owner.Config.Sources}. 117 * @return A list of variables in the {@link org.aeonbits.owner.Config.Sources} of the given {@code configurationClasses} 118 */ 119 @VisibleForTesting getConfigPathVariableNamesFromConfigClasses(final List<Class<?>> configurationClasses)120 List<String> getConfigPathVariableNamesFromConfigClasses(final List<Class<?>> configurationClasses) { 121 122 final List<String> configPathVariableNames = new ArrayList<>(); 123 124 // Loop through our classes and grab any sources with variables in there: 125 for ( final Class<?> clazz : ClassUtils.getClassesOfType(Config.class, configurationClasses) ) { 126 @SuppressWarnings("unchecked") 127 final Class<? extends Config> castedClass = (Class<? extends Config>) clazz; 128 configPathVariableNames.addAll( getSourcesAnnotationPathVariables(castedClass)); 129 } 130 131 return configPathVariableNames; 132 } 133 134 /** 135 * Get a list of the config file variables from the given {@link Config} class. 136 * @param configClass A configuration class from which to extract variable names in its {@link org.aeonbits.owner.Config.Sources}. 137 * @return A list of variables in the {@link org.aeonbits.owner.Config.Sources} of the given {@code configClass} 138 */ 139 @VisibleForTesting getSourcesAnnotationPathVariables(final Class<? extends T> configClass)140 <T extends Config> List<String> getSourcesAnnotationPathVariables(final Class<? extends T> configClass) { 141 142 final List<String> configPathVariableNames = new ArrayList<>(); 143 144 final Config.Sources annotation = configClass.getAnnotation(Config.Sources.class); 145 146 if ( annotation != null ) { 147 for (final String val : annotation.value()) { 148 149 final Matcher m = sourcesAnnotationPathVariablePattern.matcher(val); 150 if (m.find()) { 151 configPathVariableNames.add(m.group(1)); 152 } 153 } 154 } 155 156 return configPathVariableNames; 157 } 158 159 /** 160 * Injects the given property to the System Properties. 161 * Validates that this property was set after setting it. 162 * This will NOT override properties that already exist in the system. 163 * @param properties A {@link Map} of key, value pairs of properties to add to the System Properties. 164 */ 165 @VisibleForTesting injectToSystemProperties(final Map<String, String> properties)166 void injectToSystemProperties(final Map<String, String> properties) { 167 168 // Get our current system properties: 169 final Properties systemProperties = System.getProperties(); 170 171 for ( final Map.Entry<String, String> entry : properties.entrySet() ) { 172 173 // If we have this key in our system already, we do NOT set it: 174 if ( systemProperties.containsKey(entry.getKey()) ) { 175 logger.debug("System property already exists. Not overriding: " + entry.getKey()); 176 continue; 177 } 178 179 System.setProperty(entry.getKey(), entry.getValue()); 180 181 // Test for validation that it worked: 182 final String propertyValueThatWasSet = System.getProperty(entry.getKey()); 183 if (propertyValueThatWasSet == null) { 184 throw new GATKException("Unable to set System Property (" + entry.getKey() + "=" + entry.getValue() + ")!"); 185 } 186 187 if (!propertyValueThatWasSet.equals(entry.getValue())) { 188 throw new GATKException("System Property corrupted (" + entry.getKey() + "!=" + entry.getValue() + " -> " + propertyValueThatWasSet + ")!"); 189 } 190 } 191 } 192 193 // ================================================================================================================= 194 195 /** 196 * Quick way to get the GATK configuration. 197 * @return The GATK Configuration. 198 */ getGATKConfig()199 public GATKConfig getGATKConfig() { 200 return getOrCreate( GATKConfig.class ); 201 } 202 203 /** 204 * Dump the configuration to a file that can be easily read into {@link Properties}. 205 * @param config Configuration instance to dump. 206 * @param outFilePath {@link Path} to output location. 207 * @param <T> Some configuration class that extends {@link Config}. 208 */ dumpConfigSettings(final T config, final Path outFilePath )209 public static <T extends Config> void dumpConfigSettings(final T config, final Path outFilePath ) { 210 final LinkedHashMap<String, Object> configMap = getConfigMap(config, false); 211 212 final Properties properties = new Properties(); 213 properties.putAll(convertConfigMapToStringStringMap(configMap)); 214 215 final Date d = new Date(); 216 217 try ( final OutputStream outputStream = Files.newOutputStream(outFilePath, StandardOpenOption.CREATE_NEW) ) { 218 properties.store(outputStream, "Created from " + config.getClass().getSimpleName() + " at " + 219 new SimpleDateFormat("HH.mm.ss").format(d) + " on " + 220 new SimpleDateFormat("yyyy.MM.dd").format(d)); 221 } 222 catch (final Exception ex) { 223 throw new GATKException("Could not write config (" + config.getClass().getTypeName() + ") to file: " + outFilePath, ex); 224 } 225 } 226 227 /** 228 * Wrapper around {@link org.aeonbits.owner.ConfigFactory#create(Class, Map[])} which will ensure that 229 * path variables specified in {@link org.aeonbits.owner.Config.Sources} annotations are resolved prior 230 * to creation. 231 * 232 * Creates a {@link Config} instance from the specified interface 233 * 234 * @param clazz the interface extending from {@link Config} that you want to instantiate. 235 * @param imports additional variables to be used to resolve the properties. 236 * @param <T> type of the interface. 237 * @return an object implementing the given interface, which maps methods to property values. 238 */ create(final Class<? extends T> clazz, final Map<?, ?>... imports)239 public <T extends Config> T create(final Class<? extends T> clazz, final Map<?, ?>... imports) { 240 241 Utils.nonNull(clazz); 242 243 resolvePathVariables(clazz); 244 245 return org.aeonbits.owner.ConfigFactory.create(clazz, imports); 246 } 247 248 /** 249 * Wrapper around {@link ConfigCache#getOrCreate(Class, Map[])} which will ensure that 250 * path variables specified in {@link org.aeonbits.owner.Config.Sources} annotations are resolved prior 251 * to creation. 252 * 253 * Gets from the cache or create, an instance of the given class using the given imports. 254 * The factory used to create new instances is the static {@link org.aeonbits.owner.ConfigFactory#INSTANCE}. 255 * 256 * @param clazz the interface extending from {@link Config} that you want to instantiate. 257 * @param imports additional variables to be used to resolve the properties. 258 * @param <T> type of the interface. 259 * @return an object implementing the given interface, that can be taken from the cache, 260 * which maps methods to property values. 261 */ getOrCreate(final Class<? extends T> clazz, final Map<?, ?>... imports)262 public <T extends Config> T getOrCreate(final Class<? extends T> clazz, final Map<?, ?>... imports) { 263 264 Utils.nonNull(clazz); 265 266 resolvePathVariables(clazz); 267 268 return ConfigCache.getOrCreate(clazz, imports); 269 } 270 271 /** 272 * Wrapper around {@link ConfigCache#getOrCreate(Factory, Class, Map[])} which will ensure that 273 * path variables specified in {@link org.aeonbits.owner.Config.Sources} annotations are resolved prior 274 * to creation. 275 * 276 * Gets from the cache or create, an instance of the given class using the given imports. 277 * 278 * @param factory the factory to use to eventually create the instance. 279 * @param clazz the interface extending from {@link Config} that you want to instantiate. 280 * @param imports additional variables to be used to resolve the properties. 281 * @param <T> type of the interface. 282 * @return an object implementing the given interface, that can be taken from the cache, 283 * which maps methods to property values. 284 */ getOrCreate(final Factory factory, final Class<? extends T> clazz, final Map<?, ?>... imports)285 public <T extends Config> T getOrCreate(final Factory factory, final Class<? extends T> clazz, final Map<?, ?>... imports) { 286 287 Utils.nonNull(factory); 288 Utils.nonNull(clazz); 289 290 resolvePathVariables(clazz); 291 292 return ConfigCache.getOrCreate(factory, clazz, imports); 293 } 294 295 /** 296 * Wrapper around {@link ConfigCache#getOrCreate(Object, Class, Map[])} which will ensure that 297 * path variables specified in {@link org.aeonbits.owner.Config.Sources} annotations are resolved prior 298 * to creation. 299 * 300 * Gets from the cache or create, an instance of the given class using the given imports. 301 * The factory used to create new instances is the static {@link org.aeonbits.owner.ConfigFactory#INSTANCE}. 302 * 303 * @param key the key object to be used to identify the instance in the cache. 304 * @param clazz the interface extending from {@link Config} that you want to instantiate. 305 * @param imports additional variables to be used to resolve the properties. 306 * @param <T> type of the interface. 307 * @return an object implementing the given interface, that can be taken from the cache, 308 * which maps methods to property values. 309 */ getOrCreate(final Object key, final Class<? extends T> clazz, final Map<?, ?>... imports)310 public <T extends Config> T getOrCreate(final Object key, final Class<? extends T> clazz, final Map<?, ?>... imports) { 311 312 Utils.nonNull(key); 313 Utils.nonNull(clazz); 314 315 resolvePathVariables(clazz); 316 317 return ConfigCache.getOrCreate(key, clazz, imports); 318 } 319 320 /** 321 * Wrapper around {@link ConfigCache#getOrCreate(Factory, Object, Class, Map[])} which will ensure that 322 * path variables specified in {@link org.aeonbits.owner.Config.Sources} annotations are resolved prior 323 * to creation. 324 * 325 * @param factory the factory to use to eventually create the instance. 326 * @param key the key object to be used to identify the instance in the cache. 327 * @param clazz the interface extending from {@link Config} that you want to instantiate. 328 * @param imports additional variables to be used to resolve the properties. 329 * @param <T> type of the interface. 330 * @return an object implementing the given interface, that can be taken from the cache, 331 * which maps methods to property values. 332 */ getOrCreate(final Factory factory, final Object key, final Class<? extends T> clazz, final Map<?, ?>... imports)333 public <T extends Config> T getOrCreate(final Factory factory, final Object key, 334 final Class<? extends T> clazz, final Map<?, ?>... imports) { 335 336 Utils.nonNull(factory); 337 Utils.nonNull(key); 338 Utils.nonNull(clazz); 339 340 resolvePathVariables(clazz); 341 342 return ConfigCache.getOrCreate(key, clazz, imports); 343 } 344 resolvePathVariables(final Class<? extends T> clazz)345 private synchronized <T extends Config> void resolvePathVariables(final Class<? extends T> clazz) { 346 if ( !alreadyResolvedPathVariables.contains(clazz) ) { 347 checkFileNamePropertyExistenceAndSetConfigFactoryProperties( 348 getSourcesAnnotationPathVariables(clazz) 349 ); 350 351 alreadyResolvedPathVariables.add(clazz); 352 } 353 } 354 355 /** 356 * Wrapper around {@link ConfigCache#get(Object)}. 357 * This method is here to complete the interface for getting {@link Config} objects. 358 * 359 * Gets from the cache the {@link Config} instance identified by the given key. 360 * 361 * @param key the key object to be used to identify the instance in the cache. 362 * @param <T> type of the interface. 363 * @return the {@link Config} object from the cache if exists, or <tt>null</tt> if it doesn't. 364 */ get(final Object key)365 public <T extends Config> T get(final Object key) { 366 Utils.nonNull(key); 367 return ConfigCache.get(key); 368 } 369 370 /** 371 * Get the configuration file name from the given arguments. 372 * 373 * NOTE: Does NOT validate that the resulting string is a valid configuration file. 374 * 375 * @param args Command-line arguments passed to this program. 376 * @param configFileOption The command-line option indicating that the config file is next 377 * @return The name of the configuration file for this program or {@code null}. 378 */ getConfigFilenameFromArgs( final String[] args, final String configFileOption )379 public static String getConfigFilenameFromArgs( final String[] args, final String configFileOption ) { 380 381 Utils.nonNull(args); 382 Utils.nonNull(configFileOption); 383 384 String configFileName = null; 385 386 for ( int i = 0 ; i < args.length ; ++i ) { 387 if (args[i].equals(configFileOption)) { 388 389 // Get the config file name (ignoring other arguments): 390 if ( ((i+1) < args.length) && (!args[i+1].startsWith("-")) ) { 391 configFileName = args[i+1]; 392 break; 393 } 394 else { 395 // Option was provided, but no file was specified. 396 // We cannot work under these conditions: 397 throw new UserException.BadInput("ERROR: Configuration file not given after config file option specified: " + configFileOption); 398 } 399 } 400 } 401 402 return configFileName; 403 } 404 405 /** 406 * Get the configuration filename from the command-line (if it exists) and create a configuration for it. 407 * Configuration type defaults to {@link GATKConfig} 408 * Also sets system-level properties from the system config file. 409 * @param argList The list of arguments from which to read the config file. 410 * @param configFileOption The command-line option specifying the main configuration file. 411 */ initializeConfigurationsFromCommandLineArgs(final String[] argList, final String configFileOption)412 public synchronized void initializeConfigurationsFromCommandLineArgs(final String[] argList, 413 final String configFileOption) { 414 initializeConfigurationsFromCommandLineArgs( 415 argList, 416 configFileOption, 417 GATKConfig.class 418 ); 419 } 420 421 /** 422 * Get the configuration from filename the command-line (if it exists) and create a configuration for it of the given type. 423 * Also sets system-level properties from the system config file. 424 * @param argList The list of arguments from which to read the config file. 425 * @param configFileOption The command-line option specifying the main configuration file. 426 * @param configClass The class of the configuration file to instantiate. 427 */ initializeConfigurationsFromCommandLineArgs(final String[] argList, final String configFileOption, final Class<? extends T> configClass)428 public synchronized <T extends Config> void initializeConfigurationsFromCommandLineArgs(final String[] argList, 429 final String configFileOption, 430 final Class<? extends T> configClass) { 431 Utils.nonNull(argList); 432 Utils.nonNull(configFileOption); 433 Utils.nonNull(configClass); 434 435 // Get main config from args: 436 final String configFileName = getConfigFilenameFromArgs( argList, configFileOption ); 437 438 // Load the configuration from the given file: 439 final T configuration = getOrCreateConfigFromFile(configFileName, configClass); 440 441 // To start with we inject our system properties to ensure they are defined for downstream components: 442 injectSystemPropertiesFromConfig( configuration ); 443 } 444 445 @VisibleForTesting getOrCreateConfigFromFile(final String configFileName, final Class<? extends T> configClass)446 synchronized <T extends Config> T getOrCreateConfigFromFile(final String configFileName, final Class<? extends T> configClass) { 447 448 // Set the config path if we've specified it: 449 if ( configFileName != null ){ 450 org.aeonbits.owner.ConfigFactory.setProperty( GATKConfig.CONFIG_FILE_VARIABLE_FILE_NAME, configFileName ); 451 } 452 453 // Set the config file to be the one we want to use from the command-line: 454 return ConfigFactory.getInstance().getOrCreate(configClass); 455 } 456 457 @VisibleForTesting createConfigFromFile(final String configFileName, final Class<? extends T> configClass)458 synchronized <T extends Config> T createConfigFromFile(final String configFileName, final Class<? extends T> configClass) { 459 460 // Set the config path if we've specified it: 461 if ( configFileName != null ){ 462 org.aeonbits.owner.ConfigFactory.setProperty( GATKConfig.CONFIG_FILE_VARIABLE_FILE_NAME, configFileName ); 463 } 464 465 // Set the config file to be the one we want to use from the command-line: 466 return ConfigFactory.getInstance().create(configClass); 467 } 468 469 /** 470 * Injects system properties from the given configuration file. 471 * System properties are specified by the presence of the {@link SystemProperty} annotation. 472 * This will NOT override properties that already exist in the system. 473 * @param config The {@link GATKConfig} object from which to inject system properties. 474 */ injectSystemPropertiesFromConfig(final T config)475 public synchronized <T extends Config> void injectSystemPropertiesFromConfig(final T config) { 476 477 Utils.nonNull(config); 478 479 // Get our system properties: 480 final Map<String, String> properties = getSystemPropertiesFromConfig(config); 481 482 // Set our properties: 483 injectToSystemProperties(properties); 484 } 485 486 /** 487 * Logs all the parameters in the given {@link Config} object at {@link Level#DEBUG} 488 * @param config A {@link Config} object from which to log all parameters and values. 489 * @param <T> any {@link Config} type to use to log all configuration information. 490 */ logConfigFields(final T config)491 public static <T extends Config> void logConfigFields(final T config) { 492 logConfigFields(config, Log.LogLevel.DEBUG); 493 } 494 495 /** 496 * Gets all system properties from the given {@link Config}-derived object. 497 * System properties are denoted via the presence of the {@link SystemProperty} annotation. 498 * @param config A {@link Config}-derived object from which to read system properties. 499 * @param <T> A {@link Config}-derived type from which to read System Properties. 500 * @return A properties {@link Map} representing all System Properties in the given {@code config}. 501 */ 502 @VisibleForTesting getSystemPropertiesFromConfig(final T config)503 static <T extends Config> LinkedHashMap<String, String> getSystemPropertiesFromConfig(final T config) { 504 505 Utils.nonNull(config); 506 507 final LinkedHashMap<String, String> properties = new LinkedHashMap<>(); 508 509 for ( final Map.Entry<String, Object> entry : getConfigMap(config, true).entrySet() ) { 510 properties.put(entry.getKey(), String.valueOf(entry.getValue())); 511 } 512 513 return properties; 514 } 515 516 /** 517 * Logs all the parameters in the given {@link Config} object at the given {@link Log.LogLevel} 518 * @param config A {@link Config} object from which to log all parameters and values. 519 * @param logLevel The log {@link htsjdk.samtools.util.Log.LogLevel} at which to log the data in {@code config} 520 * @param <T> any {@link Config} type to use to log all configuration information. 521 */ logConfigFields(final T config, final Log.LogLevel logLevel)522 public static <T extends Config> void logConfigFields(final T config, final Log.LogLevel logLevel) { 523 524 Utils.nonNull(config); 525 Utils.nonNull(logLevel); 526 527 final Level level = LoggingUtils.levelToLog4jLevel(logLevel); 528 529 // Only continue in this method here if we would log the given level: 530 if ( !logger.isEnabled(level) ) { 531 return; 532 } 533 534 logger.log(level, "Configuration file values: "); 535 for ( final Map.Entry<String, Object> entry : getConfigMap(config, false).entrySet() ) { 536 logger.log(level, "\t" + entry.getKey() + " = " + entry.getValue()); 537 } 538 } 539 540 @VisibleForTesting getConfigMap( final T config, final boolean onlySystemProperties )541 static <T extends Config> LinkedHashMap<String, Object> getConfigMap( final T config, final boolean onlySystemProperties ) { 542 final LinkedHashMap<String, Object> configMap = new LinkedHashMap<>(); 543 544 // This is gross and uses reflection to get all methods in the given config class 545 // and then interrogates those methods for internal data on the config parameters. 546 547 // We have to match our interfaces to the config interface that we're actually using. 548 // It's not as simple as using getDeclaredMethods on the Class object because we'll get 549 // a LOT of extraneous stuff that we don't care about. 550 // So we make sure we have an interface that is a child of the OWNER Config interface. 551 for ( final Class<?> classInterface : ClassUtils.getClassesOfType(Config.class, Arrays.asList(config.getClass().getInterfaces())) ) { 552 553 // Now we cycle through our interface methods, resolve parameter names, 554 // and log their values at the given level: 555 for (final Method propertyMethod : classInterface.getDeclaredMethods()) { 556 557 // Get the property name: 558 String propertyName = propertyMethod.getName(); 559 560 // Get the real property name if we've overwritten it with a key: 561 final Config.Key key = propertyMethod.getAnnotation(Config.Key.class); 562 if (key != null) { 563 propertyName = key.value(); 564 } 565 566 try { 567 if ( onlySystemProperties ) { 568 if ( propertyMethod.isAnnotationPresent(SystemProperty.class) ) { 569 configMap.put(propertyName, propertyMethod.invoke(config)); 570 } 571 } 572 else { 573 configMap.put(propertyName, propertyMethod.invoke(config)); 574 } 575 } catch (final IllegalAccessException ex) { 576 throw new GATKException("Could not access the config getter: " + 577 config.getClass().getSimpleName() + "." + 578 propertyMethod.getName(), ex); 579 580 } catch (final InvocationTargetException ex) { 581 throw new GATKException("Could not invoke the config getter: " + 582 config.getClass().getSimpleName() + "." + 583 propertyMethod.getName(), ex); 584 } 585 } 586 } 587 588 return configMap; 589 } 590 convertConfigMapToStringStringMap(final LinkedHashMap<String, Object> configMap)591 private static LinkedHashMap<String, String> convertConfigMapToStringStringMap(final LinkedHashMap<String, Object> configMap) { 592 final LinkedHashMap<String, String> map = new LinkedHashMap<>(); 593 594 for ( final Map.Entry<String, Object> entry : configMap.entrySet() ){ 595 map.put( entry.getKey(), entry.getValue().toString() ); 596 } 597 598 return map; 599 } 600 } 601