1 // This file is part of OpenTSDB. 2 // Copyright (C) 2010-2012 The OpenTSDB Authors. 3 // 4 // This program is free software: you can redistribute it and/or modify it 5 // under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 2.1 of the License, or (at your 7 // option) any later version. This program is distributed in the hope that it 8 // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 9 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License for more details. You should have received a copy 11 // of the GNU Lesser General Public License along with this program. If not, 12 // see <http://www.gnu.org/licenses/>. 13 package net.opentsdb.utils; 14 15 import java.io.FileInputStream; 16 import java.io.FileNotFoundException; 17 import java.io.IOException; 18 import java.util.ArrayList; 19 import java.util.Enumeration; 20 import java.util.HashMap; 21 import java.util.Map; 22 import java.util.Properties; 23 24 import org.slf4j.Logger; 25 import org.slf4j.LoggerFactory; 26 27 import com.google.common.collect.ImmutableMap; 28 29 /** 30 * OpenTSDB Configuration Class 31 * 32 * This handles all of the user configurable variables for a TSD. On 33 * initialization default values are configured for all variables. Then 34 * implementations should call the {@link #loadConfig()} methods to search for a 35 * default configuration or try to load one provided by the user. 36 * 37 * To add a configuration, simply set a default value in {@link #setDefaults()}. 38 * Wherever you need to access the config value, use the proper helper to fetch 39 * the value, accounting for exceptions that may be thrown if necessary. 40 * 41 * The get<type> number helpers will return NumberFormatExceptions if the 42 * requested property is null or unparseable. The {@link #getString(String)} 43 * helper will return a NullPointerException if the property isn't found. 44 * <p> 45 * Plugins can extend this class and copy the properties from the main 46 * TSDB.config instance. Plugins should never change the main TSD's config 47 * properties, rather a plugin should use the Config(final Config parent) 48 * constructor to get a copy of the parent's properties and then work with the 49 * values locally. 50 * @since 2.0 51 */ 52 public class Config { 53 private static final Logger LOG = LoggerFactory.getLogger(Config.class); 54 55 /** Flag to determine if we're running under Windows or not */ 56 public static final boolean IS_WINDOWS = 57 System.getProperty("os.name", "").contains("Windows"); 58 59 // These are accessed often so need a set address for fast access (faster 60 // than accessing the map. Their value will be changed when the config is 61 // loaded 62 // NOTE: edit the setDefaults() method if you add a public field 63 64 /** tsd.core.auto_create_metrics */ 65 private boolean auto_metric = false; 66 67 /** tsd.core.auto_create_tagk */ 68 private boolean auto_tagk = true; 69 70 /** tsd.core.auto_create_tagv */ 71 private boolean auto_tagv = true; 72 73 /** tsd.storage.enable_compaction */ 74 private boolean enable_compactions = true; 75 76 /** tsd.storage.enable_appends */ 77 private boolean enable_appends = false; 78 79 /** tsd.storage.repair_appends */ 80 private boolean repair_appends = false; 81 82 /** tsd.core.meta.enable_realtime_ts */ 83 private boolean enable_realtime_ts = false; 84 85 /** tsd.core.meta.enable_realtime_uid */ 86 private boolean enable_realtime_uid = false; 87 88 /** tsd.core.meta.enable_tsuid_incrementing */ 89 private boolean enable_tsuid_incrementing = false; 90 91 /** tsd.core.meta.enable_tsuid_tracking */ 92 private boolean enable_tsuid_tracking = false; 93 94 /** tsd.http.request.enable_chunked */ 95 private boolean enable_chunked_requests = false; 96 97 /** tsd.storage.fix_duplicates */ 98 private boolean fix_duplicates = false; 99 100 /** tsd.http.request.max_chunk */ 101 private int max_chunked_requests = 4096; 102 103 /** tsd.core.tree.enable_processing */ 104 private boolean enable_tree_processing = false; 105 106 /** tsd.storage.hbase.scanner.maxNumRows */ 107 private int scanner_max_num_rows = 128; 108 109 /** 110 * The list of properties configured to their defaults or modified by users 111 */ 112 protected final HashMap<String, String> properties = 113 new HashMap<String, String>(); 114 115 /** Holds default values for the config */ 116 protected static final HashMap<String, String> default_map = 117 new HashMap<String, String>(); 118 119 /** Tracks the location of the file that was actually loaded */ 120 protected String config_location; 121 122 /** 123 * Constructor that initializes default configuration values. May attempt to 124 * search for a config file if configured. 125 * @param auto_load_config When set to true, attempts to search for a config 126 * file in the default locations 127 * @throws IOException Thrown if unable to read or parse one of the default 128 * config files 129 */ Config(final boolean auto_load_config)130 public Config(final boolean auto_load_config) throws IOException { 131 if (auto_load_config) { 132 loadConfig(); 133 } 134 setDefaults(); 135 } 136 137 /** 138 * Constructor that initializes default values and attempts to load the given 139 * properties file 140 * @param file Path to the file to load 141 * @throws IOException Thrown if unable to read or parse the file 142 */ Config(final String file)143 public Config(final String file) throws IOException { 144 loadConfig(file); 145 setDefaults(); 146 } 147 148 /** 149 * Constructor for plugins or overloaders who want a copy of the parent 150 * properties but without the ability to modify them 151 * 152 * This constructor will not re-read the file, but it will copy the location 153 * so if a child wants to reload the properties periodically, they may do so 154 * @param parent Parent configuration object to load from 155 */ Config(final Config parent)156 public Config(final Config parent) { 157 // copy so changes to the local props by the plugin don't affect the master 158 properties.putAll(parent.properties); 159 config_location = parent.config_location; 160 setDefaults(); 161 } 162 163 /** @return The file that generated this config. May be null */ configLocation()164 public String configLocation() { 165 return config_location; 166 } 167 168 /** @return the auto_metric value */ auto_metric()169 public boolean auto_metric() { 170 return auto_metric; 171 } 172 173 /** @return the auto_tagk value */ auto_tagk()174 public boolean auto_tagk() { 175 return auto_tagk; 176 } 177 178 /** @return the auto_tagv value */ auto_tagv()179 public boolean auto_tagv() { 180 return auto_tagv; 181 } 182 183 /** @param auto_metric whether or not to auto create metrics */ setAutoMetric(boolean auto_metric)184 public void setAutoMetric(boolean auto_metric) { 185 this.auto_metric = auto_metric; 186 properties.put("tsd.core.auto_create_metrics", 187 Boolean.toString(auto_metric)); 188 } 189 190 /** @return the enable_compaction value */ enable_compactions()191 public boolean enable_compactions() { 192 return enable_compactions; 193 } 194 195 /** @return whether or not to write data in the append format */ enable_appends()196 public boolean enable_appends() { 197 return enable_appends; 198 } 199 200 /** @return whether or not to re-write appends with duplicates or out of order 201 * data when queried. */ repair_appends()202 public boolean repair_appends() { 203 return repair_appends; 204 } 205 206 /** @return whether or not to record new TSMeta objects in real time */ enable_realtime_ts()207 public boolean enable_realtime_ts() { 208 return enable_realtime_ts; 209 } 210 211 /** @return whether or not record new UIDMeta objects in real time */ enable_realtime_uid()212 public boolean enable_realtime_uid() { 213 return enable_realtime_uid; 214 } 215 216 /** @return whether or not to increment TSUID counters */ enable_tsuid_incrementing()217 public boolean enable_tsuid_incrementing() { 218 return enable_tsuid_incrementing; 219 } 220 221 /** @return whether or not to record a 1 for every TSUID */ enable_tsuid_tracking()222 public boolean enable_tsuid_tracking() { 223 return enable_tsuid_tracking; 224 } 225 226 /** @return maximum number of rows to be fetched per round trip while scanning HBase */ scanner_maxNumRows()227 public int scanner_maxNumRows() { 228 return scanner_max_num_rows; 229 } 230 231 /** @return whether or not chunked requests are supported */ enable_chunked_requests()232 public boolean enable_chunked_requests() { 233 return enable_chunked_requests; 234 } 235 236 /** @return max incoming chunk size in bytes */ max_chunked_requests()237 public int max_chunked_requests() { 238 return max_chunked_requests; 239 } 240 241 /** @return true if duplicate values should be fixed */ fix_duplicates()242 public boolean fix_duplicates() { 243 return fix_duplicates; 244 } 245 246 /** @param fix_duplicates true if duplicate values should be fixed */ setFixDuplicates(final boolean fix_duplicates)247 public void setFixDuplicates(final boolean fix_duplicates) { 248 this.fix_duplicates = fix_duplicates; 249 } 250 251 /** @return whether or not to process new or updated TSMetas through trees */ enable_tree_processing()252 public boolean enable_tree_processing() { 253 return enable_tree_processing; 254 } 255 256 /** 257 * Allows for modifying properties after creation or loading. 258 * 259 * WARNING: This should only be used on initialization and is meant for 260 * command line overrides. Also note that it will reset all static config 261 * variables when called. 262 * 263 * @param property The name of the property to override 264 * @param value The value to store 265 */ overrideConfig(final String property, final String value)266 public void overrideConfig(final String property, final String value) { 267 properties.put(property, value); 268 loadStaticVariables(); 269 } 270 271 /** 272 * Returns the given property as a String 273 * @param property The property to load 274 * @return The property value as a string 275 * @throws NullPointerException if the property did not exist 276 */ getString(final String property)277 public final String getString(final String property) { 278 return properties.get(property); 279 } 280 281 /** 282 * Returns the given property as an integer 283 * @param property The property to load 284 * @return A parsed integer or an exception if the value could not be parsed 285 * @throws NumberFormatException if the property could not be parsed 286 * @throws NullPointerException if the property did not exist 287 */ getInt(final String property)288 public final int getInt(final String property) { 289 return Integer.parseInt(sanitize(properties.get(property))); 290 } 291 292 /** 293 * Returns the given string trimed or null if is null 294 * @param string The string be trimmed of 295 * @return The string trimed or null 296 */ sanitize(final String string)297 private final String sanitize(final String string) { 298 if (string == null) { 299 return null; 300 } 301 302 return string.trim(); 303 } 304 305 /** 306 * Returns the given property as a short 307 * @param property The property to load 308 * @return A parsed short or an exception if the value could not be parsed 309 * @throws NumberFormatException if the property could not be parsed 310 * @throws NullPointerException if the property did not exist 311 */ getShort(final String property)312 public final short getShort(final String property) { 313 return Short.parseShort(sanitize(properties.get(property))); 314 } 315 316 /** 317 * Returns the given property as a long 318 * @param property The property to load 319 * @return A parsed long or an exception if the value could not be parsed 320 * @throws NumberFormatException if the property could not be parsed 321 * @throws NullPointerException if the property did not exist 322 */ getLong(final String property)323 public final long getLong(final String property) { 324 return Long.parseLong(sanitize(properties.get(property))); 325 } 326 327 /** 328 * Returns the given property as a float 329 * @param property The property to load 330 * @return A parsed float or an exception if the value could not be parsed 331 * @throws NumberFormatException if the property could not be parsed 332 * @throws NullPointerException if the property did not exist 333 */ getFloat(final String property)334 public final float getFloat(final String property) { 335 return Float.parseFloat(sanitize(properties.get(property))); 336 } 337 338 /** 339 * Returns the given property as a double 340 * @param property The property to load 341 * @return A parsed double or an exception if the value could not be parsed 342 * @throws NumberFormatException if the property could not be parsed 343 * @throws NullPointerException if the property did not exist 344 */ getDouble(final String property)345 public final double getDouble(final String property) { 346 return Double.parseDouble(sanitize(properties.get(property))); 347 } 348 349 /** 350 * Returns the given property as a boolean 351 * 352 * Property values are case insensitive and the following values will result 353 * in a True return value: - 1 - True - Yes 354 * 355 * Any other values, including an empty string, will result in a False 356 * 357 * @param property The property to load 358 * @return A parsed boolean 359 * @throws NullPointerException if the property was not found 360 */ getBoolean(final String property)361 public final boolean getBoolean(final String property) { 362 final String val = properties.get(property).trim().toUpperCase(); 363 if (val.equals("1")) 364 return true; 365 if (val.equals("TRUE")) 366 return true; 367 if (val.equals("YES")) 368 return true; 369 return false; 370 } 371 372 /** 373 * Returns the directory name, making sure the end is an OS dependent slash 374 * @param property The property to load 375 * @return The property value with a forward or back slash appended or null 376 * if the property wasn't found or the directory was empty. 377 */ getDirectoryName(final String property)378 public final String getDirectoryName(final String property) { 379 String directory = properties.get(property); 380 if (directory == null || directory.isEmpty()){ 381 return null; 382 } 383 if (IS_WINDOWS) { 384 // Windows swings both ways. If a forward slash was already used, we'll 385 // add one at the end if missing. Otherwise use the windows default of \ 386 if (directory.charAt(directory.length() - 1) == '\\' || 387 directory.charAt(directory.length() - 1) == '/') { 388 return directory; 389 } 390 if (directory.contains("/")) { 391 return directory + "/"; 392 } 393 return directory + "\\"; 394 } 395 if (directory.contains("\\")) { 396 throw new IllegalArgumentException( 397 "Unix path names cannot contain a back slash"); 398 } 399 400 if (directory == null || directory.isEmpty()){ 401 return null; 402 } 403 404 if (directory.charAt(directory.length() - 1) == '/') { 405 return directory; 406 } 407 return directory + "/"; 408 } 409 410 /** 411 * Determines if the given propery is in the map 412 * @param property The property to search for 413 * @return True if the property exists and has a value, not an empty string 414 */ hasProperty(final String property)415 public final boolean hasProperty(final String property) { 416 final String val = properties.get(property); 417 if (val == null) 418 return false; 419 if (val.isEmpty()) 420 return false; 421 return true; 422 } 423 424 /** 425 * Returns a simple string with the configured properties for debugging 426 * @return A string with information about the config 427 */ dumpConfiguration()428 public final String dumpConfiguration() { 429 if (properties.isEmpty()) 430 return "No configuration settings stored"; 431 432 StringBuilder response = new StringBuilder("TSD Configuration:\n"); 433 response.append("File [" + config_location + "]\n"); 434 int line = 0; 435 for (Map.Entry<String, String> entry : properties.entrySet()) { 436 if (line > 0) { 437 response.append("\n"); 438 } 439 response.append("Key [" + entry.getKey() + "] Value ["); 440 if (entry.getKey().toUpperCase().contains("PASS")) { 441 response.append("********"); 442 } else { 443 response.append(entry.getValue()); 444 } 445 response.append("]"); 446 line++; 447 } 448 return response.toString(); 449 } 450 451 /** @return An immutable copy of the configuration map */ getMap()452 public final Map<String, String> getMap() { 453 return ImmutableMap.copyOf(properties); 454 } 455 456 /** 457 * set enable_compactions to true 458 */ enableCompactions()459 public final void enableCompactions() { 460 this.enable_compactions = true; 461 } 462 463 /** 464 * set enable_compactions to false 465 */ disableCompactions()466 public final void disableCompactions() { 467 this.enable_compactions = false; 468 } 469 470 /** 471 * Loads default entries that were not provided by a file or command line 472 * 473 * This should be called in the constructor 474 */ setDefaults()475 protected void setDefaults() { 476 // map.put("tsd.network.port", ""); // does not have a default, required 477 // map.put("tsd.http.cachedir", ""); // does not have a default, required 478 // map.put("tsd.http.staticroot", ""); // does not have a default, required 479 default_map.put("tsd.mode", "rw"); 480 default_map.put("tsd.no_diediedie", "false"); 481 default_map.put("tsd.network.bind", "0.0.0.0"); 482 default_map.put("tsd.network.worker_threads", ""); 483 default_map.put("tsd.network.async_io", "true"); 484 default_map.put("tsd.network.tcp_no_delay", "true"); 485 default_map.put("tsd.network.keep_alive", "true"); 486 default_map.put("tsd.network.reuse_address", "true"); 487 default_map.put("tsd.core.auto_create_metrics", "false"); 488 default_map.put("tsd.core.auto_create_tagks", "true"); 489 default_map.put("tsd.core.auto_create_tagvs", "true"); 490 default_map.put("tsd.core.connections.limit", "0"); 491 default_map.put("tsd.core.enable_api", "true"); 492 default_map.put("tsd.core.enable_ui", "true"); 493 default_map.put("tsd.core.meta.enable_realtime_ts", "false"); 494 default_map.put("tsd.core.meta.enable_realtime_uid", "false"); 495 default_map.put("tsd.core.meta.enable_tsuid_incrementing", "false"); 496 default_map.put("tsd.core.meta.enable_tsuid_tracking", "false"); 497 default_map.put("tsd.core.meta.cache.enable", "false"); 498 default_map.put("tsd.core.plugin_path", ""); 499 default_map.put("tsd.core.socket.timeout", "0"); 500 default_map.put("tsd.core.tree.enable_processing", "false"); 501 default_map.put("tsd.core.preload_uid_cache", "false"); 502 default_map.put("tsd.core.preload_uid_cache.max_entries", "300000"); 503 default_map.put("tsd.core.storage_exception_handler.enable", "false"); 504 default_map.put("tsd.core.uid.random_metrics", "false"); 505 default_map.put("tsd.query.filter.expansion_limit", "4096"); 506 default_map.put("tsd.query.skip_unresolved_tagvs", "false"); 507 default_map.put("tsd.query.allow_simultaneous_duplicates", "true"); 508 default_map.put("tsd.query.enable_fuzzy_filter", "true"); 509 default_map.put("tsd.rtpublisher.enable", "false"); 510 default_map.put("tsd.rtpublisher.plugin", ""); 511 default_map.put("tsd.search.enable", "false"); 512 default_map.put("tsd.search.plugin", ""); 513 default_map.put("tsd.stats.canonical", "false"); 514 default_map.put("tsd.startup.enable", "false"); 515 default_map.put("tsd.startup.plugin", ""); 516 default_map.put("tsd.storage.hbase.scanner.maxNumRows", "128"); 517 default_map.put("tsd.storage.fix_duplicates", "false"); 518 default_map.put("tsd.storage.flush_interval", "1000"); 519 default_map.put("tsd.storage.hbase.data_table", "tsdb"); 520 default_map.put("tsd.storage.hbase.uid_table", "tsdb-uid"); 521 default_map.put("tsd.storage.hbase.tree_table", "tsdb-tree"); 522 default_map.put("tsd.storage.hbase.meta_table", "tsdb-meta"); 523 default_map.put("tsd.storage.hbase.zk_quorum", "localhost"); 524 default_map.put("tsd.storage.hbase.zk_basedir", "/hbase"); 525 default_map.put("tsd.storage.hbase.prefetch_meta", "false"); 526 default_map.put("tsd.storage.enable_appends", "false"); 527 default_map.put("tsd.storage.repair_appends", "false"); 528 default_map.put("tsd.storage.enable_compaction", "true"); 529 default_map.put("tsd.storage.compaction.flush_interval", "10"); 530 default_map.put("tsd.storage.compaction.min_flush_threshold", "100"); 531 default_map.put("tsd.storage.compaction.max_concurrent_flushes", "10000"); 532 default_map.put("tsd.storage.compaction.flush_speed", "2"); 533 default_map.put("tsd.timeseriesfilter.enable", "false"); 534 default_map.put("tsd.uidfilter.enable", "false"); 535 default_map.put("tsd.core.stats_with_port", "false"); 536 default_map.put("tsd.http.show_stack_trace", "true"); 537 default_map.put("tsd.http.query.allow_delete", "false"); 538 default_map.put("tsd.http.request.enable_chunked", "false"); 539 default_map.put("tsd.http.request.max_chunk", "4096"); 540 default_map.put("tsd.http.request.cors_domains", ""); 541 default_map.put("tsd.http.request.cors_headers", "Authorization, " 542 + "Content-Type, Accept, Origin, User-Agent, DNT, Cache-Control, " 543 + "X-Mx-ReqToken, Keep-Alive, X-Requested-With, If-Modified-Since"); 544 default_map.put("tsd.query.timeout", "0"); 545 546 for (Map.Entry<String, String> entry : default_map.entrySet()) { 547 if (!properties.containsKey(entry.getKey())) 548 properties.put(entry.getKey(), entry.getValue()); 549 } 550 551 loadStaticVariables(); 552 } 553 554 /** 555 * Searches a list of locations for a valid opentsdb.conf file 556 * 557 * The config file must be a standard JAVA properties formatted file. If none 558 * of the locations have a config file, then the defaults or command line 559 * arguments will be used for the configuration 560 * 561 * Defaults for Linux based systems are: ./opentsdb.conf /etc/opentsdb.conf 562 * /etc/opentsdb/opentdsb.conf /opt/opentsdb/opentsdb.conf 563 * 564 * @throws IOException Thrown if there was an issue reading a file 565 */ loadConfig()566 protected void loadConfig() throws IOException { 567 if (config_location != null && !config_location.isEmpty()) { 568 loadConfig(config_location); 569 return; 570 } 571 572 final ArrayList<String> file_locations = new ArrayList<String>(); 573 574 // search locally first 575 file_locations.add("opentsdb.conf"); 576 577 // add default locations based on OS 578 if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")) { 579 file_locations.add("C:\\Program Files\\opentsdb\\opentsdb.conf"); 580 file_locations.add("C:\\Program Files (x86)\\opentsdb\\opentsdb.conf"); 581 } else { 582 file_locations.add("/etc/opentsdb.conf"); 583 file_locations.add("/etc/opentsdb/opentsdb.conf"); 584 file_locations.add("/usr/local/etc/opentsdb/opentsdb.conf"); 585 file_locations.add("/opt/opentsdb/opentsdb.conf"); 586 } 587 588 for (String file : file_locations) { 589 try { 590 FileInputStream file_stream = new FileInputStream(file); 591 Properties props = new Properties(); 592 props.load(file_stream); 593 594 // load the hash map 595 loadHashMap(props); 596 } catch (Exception e) { 597 // don't do anything, the file may be missing and that's fine 598 LOG.debug("Unable to find or load " + file, e); 599 continue; 600 } 601 602 // no exceptions thrown, so save the valid path and exit 603 LOG.info("Successfully loaded configuration file: " + file); 604 config_location = file; 605 return; 606 } 607 608 LOG.info("No configuration found, will use defaults"); 609 } 610 611 /** 612 * Attempts to load the configuration from the given location 613 * @param file Path to the file to load 614 * @throws IOException Thrown if there was an issue reading the file 615 * @throws FileNotFoundException Thrown if the config file was not found 616 */ loadConfig(final String file)617 protected void loadConfig(final String file) throws FileNotFoundException, 618 IOException { 619 final FileInputStream file_stream = new FileInputStream(file); 620 try { 621 final Properties props = new Properties(); 622 props.load(file_stream); 623 624 // load the hash map 625 loadHashMap(props); 626 627 // no exceptions thrown, so save the valid path and exit 628 LOG.info("Successfully loaded configuration file: " + file); 629 config_location = file; 630 } finally { 631 file_stream.close(); 632 } 633 } 634 635 /** 636 * Loads the static class variables for values that are called often. This 637 * should be called any time the configuration changes. 638 */ loadStaticVariables()639 protected void loadStaticVariables() { 640 auto_metric = this.getBoolean("tsd.core.auto_create_metrics"); 641 auto_tagk = this.getBoolean("tsd.core.auto_create_tagks"); 642 auto_tagv = this.getBoolean("tsd.core.auto_create_tagvs"); 643 enable_compactions = this.getBoolean("tsd.storage.enable_compaction"); 644 enable_appends = this.getBoolean("tsd.storage.enable_appends"); 645 repair_appends = this.getBoolean("tsd.storage.repair_appends"); 646 enable_chunked_requests = this.getBoolean("tsd.http.request.enable_chunked"); 647 enable_realtime_ts = this.getBoolean("tsd.core.meta.enable_realtime_ts"); 648 enable_realtime_uid = this.getBoolean("tsd.core.meta.enable_realtime_uid"); 649 enable_tsuid_incrementing = 650 this.getBoolean("tsd.core.meta.enable_tsuid_incrementing"); 651 enable_tsuid_tracking = 652 this.getBoolean("tsd.core.meta.enable_tsuid_tracking"); 653 if (this.hasProperty("tsd.http.request.max_chunk")) { 654 max_chunked_requests = this.getInt("tsd.http.request.max_chunk"); 655 } 656 enable_tree_processing = this.getBoolean("tsd.core.tree.enable_processing"); 657 fix_duplicates = this.getBoolean("tsd.storage.fix_duplicates"); 658 scanner_max_num_rows = this.getInt("tsd.storage.hbase.scanner.maxNumRows"); 659 } 660 661 /** 662 * Called from {@link #loadConfig} to copy the properties into the hash map 663 * Tsuna points out that the Properties class is much slower than a hash 664 * map so if we'll be looking up config values more than once, a hash map 665 * is the way to go 666 * @param props The loaded Properties object to copy 667 */ loadHashMap(final Properties props)668 private void loadHashMap(final Properties props) { 669 properties.clear(); 670 671 @SuppressWarnings("rawtypes") 672 Enumeration e = props.propertyNames(); 673 while (e.hasMoreElements()) { 674 String key = (String) e.nextElement(); 675 properties.put(key, props.getProperty(key)); 676 } 677 } 678 } 679