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