001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.configuration; 029 030import org.opencms.i18n.CmsEncoder; 031import org.opencms.util.CmsStringUtil; 032 033import java.io.FileInputStream; 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.InputStreamReader; 037import java.io.LineNumberReader; 038import java.io.Reader; 039import java.io.UnsupportedEncodingException; 040import java.util.AbstractMap; 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.List; 045import java.util.Map; 046import java.util.Properties; 047import java.util.Set; 048import java.util.StringTokenizer; 049import java.util.TreeMap; 050 051import org.dom4j.Element; 052 053/** 054 * Provides convenient access to configuration parameters.<p> 055 * 056 * Usually the parameters are configured in some sort of String based file, 057 * either in an XML configuration, or in a .property file. 058 * This wrapper allows accessing such String values directly 059 * as <code>int</code>, <code>boolean</code> or other data types, without 060 * worrying about the type conversion.<p> 061 * 062 * It can also read a configuration from a special property file format, 063 * which is explained here: 064 * 065 * <ul> 066 * <li> 067 * Each parameter in the file has the syntax <code>key = value</code> 068 * </li> 069 * <li> 070 * The <i>key</i> may use any character but the equal sign '='. 071 * </li> 072 * <li> 073 * <i>value</i> may be separated on different lines if a backslash 074 * is placed at the end of the line that continues below. 075 * </li> 076 * <li> 077 * If <i>value</i> is a list of strings, each token is separated 078 * by a comma ','. 079 * </li> 080 * <li> 081 * Commas in each token are escaped placing a backslash right before 082 * the comma. 083 * </li> 084 * <li> 085 * Backslashes are escaped by using two consecutive backslashes i.e. \\. 086 * Note: Unlike in regular Java properties files, you don't need to escape Backslashes. 087 * </li> 088 * <li> 089 * If a <i>key</i> is used more than once, the values are appended 090 * as if they were on the same line separated with commas. 091 * </li> 092 * <li> 093 * Blank lines and lines starting with character '#' are skipped. 094 * </li> 095 * </ul> 096 * 097 * Here is an example of a valid parameter properties file:<p> 098 * 099 * <pre> 100 * # lines starting with # are comments 101 * 102 * # This is the simplest property 103 * key = value 104 * 105 * # A long property may be separated on multiple lines 106 * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ 107 * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 108 * 109 * # This is a property with many tokens 110 * tokens_on_a_line = first token, second token 111 * 112 * # This sequence generates exactly the same result 113 * tokens_on_multiple_lines = first token 114 * tokens_on_multiple_lines = second token 115 * 116 * # commas may be escaped in tokens 117 * commas.escaped = Hi\, what'up? 118 * </pre> 119 */ 120public class CmsParameterConfiguration extends AbstractMap<String, String> { 121 122 /** 123 * Used to read parameter lines from a property file.<p> 124 * 125 * The lines do not terminate with new-line chars but rather when there is no 126 * backslash sign a the end of the line. This is used to 127 * concatenate multiple lines for readability in the input file.<p> 128 */ 129 protected static class ParameterReader extends LineNumberReader { 130 131 /** 132 * Constructor.<p> 133 * 134 * @param reader a reader 135 */ 136 public ParameterReader(Reader reader) { 137 138 super(reader); 139 } 140 141 /** 142 * Reads a parameter line.<p> 143 * 144 * @return the parameter line read 145 * 146 * @throws IOException in case of IO errors 147 */ 148 public String readParameter() throws IOException { 149 150 StringBuffer buffer = new StringBuffer(); 151 String line = readLine(); 152 while (line != null) { 153 line = line.trim(); 154 if ((line.length() != 0) && (line.charAt(0) != '#')) { 155 if (endsWithSlash(line)) { 156 line = line.substring(0, line.length() - 1); 157 buffer.append(line); 158 } else { 159 buffer.append(line); 160 return buffer.toString(); // normal method end 161 } 162 } 163 line = readLine(); 164 } 165 return null; // EOF reached 166 } 167 } 168 169 /** 170 * This class divides property value into tokens separated by ",".<p> 171 * 172 * Commas in the property value that are wanted 173 * can be escaped using the backslash in front like this "\,". 174 */ 175 protected static class ParameterTokenizer extends StringTokenizer { 176 177 /** The property delimiter used while parsing (a comma). */ 178 static final String COMMA = ","; 179 180 /** 181 * Constructor.<p> 182 * 183 * @param string the String to break into tokens 184 */ 185 public ParameterTokenizer(String string) { 186 187 super(string, COMMA); 188 } 189 190 /** 191 * Returns the next token.<p> 192 * 193 * @return the next token 194 */ 195 @Override 196 public String nextToken() { 197 198 StringBuffer buffer = new StringBuffer(); 199 200 while (hasMoreTokens()) { 201 String token = super.nextToken(); 202 if (endsWithSlash(token)) { 203 buffer.append(token.substring(0, token.length() - 1)); 204 buffer.append(COMMA); 205 } else { 206 buffer.append(token); 207 break; 208 } 209 } 210 211 return buffer.toString().trim(); 212 } 213 } 214 215 /** 216 * An empty, immutable parameter configuration.<p> 217 */ 218 public static final CmsParameterConfiguration EMPTY_PARAMETERS = new CmsParameterConfiguration( 219 Collections.<String, String> emptyMap(), 220 Collections.<String, Object> emptyMap()); 221 222 /** The parsed map of parameters where the Strings may have become Objects. */ 223 private Map<String, Object> m_configurationObjects; 224 225 /** The original map of parameters that contains only String values. */ 226 private Map<String, String> m_configurationStrings; 227 228 /** 229 * Creates an empty parameter configuration.<p> 230 */ 231 public CmsParameterConfiguration() { 232 233 this(new TreeMap<String, String>(), new TreeMap<String, Object>()); 234 } 235 236 /** 237 * Creates a parameter configuration from an input stream.<p> 238 * 239 * @param in the input stream to create the parameter configuration from 240 * 241 * @throws IOException in case of errors loading the parameters from the input stream 242 */ 243 public CmsParameterConfiguration(InputStream in) 244 throws IOException { 245 246 this(); 247 load(in); 248 } 249 250 /** 251 * Creates a parameter configuration from a Map of Strings.<p> 252 * 253 * @param configuration the map of Strings to create the parameter configuration from 254 */ 255 public CmsParameterConfiguration(Map<String, String> configuration) { 256 257 this(); 258 259 for (String key : configuration.keySet()) { 260 261 String value = configuration.get(key); 262 add(key, value); 263 } 264 } 265 266 /** 267 * Creates a parameter wrapper by loading the parameters from the specified property file.<p> 268 * 269 * @param file the path of the file to load 270 * 271 * @throws IOException in case of errors loading the parameters from the specified property file 272 */ 273 public CmsParameterConfiguration(String file) 274 throws IOException { 275 276 this(); 277 278 FileInputStream in = null; 279 try { 280 in = new FileInputStream(file); 281 load(in); 282 } finally { 283 try { 284 if (in != null) { 285 in.close(); 286 } 287 } catch (IOException ex) { 288 // ignore error on close() only 289 } 290 } 291 } 292 293 /** 294 * Creates a parameter configuration from the given maps.<p> 295 * 296 * @param strings the String map 297 * @param objects the object map 298 */ 299 private CmsParameterConfiguration(Map<String, String> strings, Map<String, Object> objects) { 300 301 m_configurationStrings = strings; 302 m_configurationObjects = objects; 303 } 304 305 /** 306 * Returns an unmodifiable version of this parameter configuration.<p> 307 * 308 * @param original the configuration to make unmodifiable 309 * 310 * @return an unmodifiable version of this parameter configuration 311 */ 312 public static CmsParameterConfiguration unmodifiableVersion(CmsParameterConfiguration original) { 313 314 return new CmsParameterConfiguration( 315 Collections.unmodifiableMap(original.m_configurationStrings), 316 original.m_configurationObjects); 317 } 318 319 /** 320 * Counts the number of successive times 'ch' appears in the 321 * 'line' before the position indicated by the 'index'.<p> 322 * 323 * @param line the line to count 324 * @param index the index position to start 325 * @param ch the character to count 326 * 327 * @return the number of successive times 'ch' appears in the 'line' 328 * before the position indicated by the 'index' 329 */ 330 protected static int countPreceding(String line, int index, char ch) { 331 332 int i; 333 for (i = index - 1; i >= 0; i--) { 334 if (line.charAt(i) != ch) { 335 break; 336 } 337 } 338 return index - 1 - i; 339 } 340 341 /** 342 * Checks if the line ends with odd number of backslashes.<p> 343 * 344 * @param line the line to check 345 * 346 * @return <code>true</code> if the line ends with odd number of backslashes 347 */ 348 protected static boolean endsWithSlash(String line) { 349 350 if (!line.endsWith("\\")) { 351 return false; 352 } 353 return ((countPreceding(line, line.length() - 1, '\\') % 2) == 0); 354 } 355 356 /** 357 * Replaces escaped char sequences in the input value.<p> 358 * 359 * @param value the value to unescape 360 * 361 * @return the unescaped String 362 */ 363 protected static String unescape(String value) { 364 365 value = CmsStringUtil.substitute(value, "\\,", ","); 366 value = CmsStringUtil.substitute(value, "\\=", "="); 367 value = CmsStringUtil.substitute(value, "\\\\", "\\"); 368 369 return value; 370 } 371 372 /** 373 * Add a parameter to this configuration.<p> 374 * 375 * If the parameter already exists then the value will be added 376 * to the existing configuration entry and a List will be created for the values.<p> 377 * 378 * String values separated by a comma "," will NOT be tokenized when this 379 * method is used. To create a List of String values for a parameter, call this method 380 * multiple times with the same parameter name.<p> 381 * 382 * @param key the parameter to add 383 * @param value the value to add 384 */ 385 public void add(String key, String value) { 386 387 add(key, value, false); 388 } 389 390 /** 391 * Serializes this parameter configuration for the OpenCms XML configuration.<p> 392 * 393 * For each parameter, a XML node like this<br> 394 * <code> 395 * <param name="theName">theValue</param> 396 * </code><br> 397 * is generated and appended to the provided parent node.<p> 398 * 399 * @param parentNode the parent node where the parameter nodes are appended to 400 * 401 * @return the parent node 402 */ 403 public Element appendToXml(Element parentNode) { 404 405 return appendToXml(parentNode, null); 406 } 407 408 /** 409 * Serializes this parameter configuration for the OpenCms XML configuration.<p> 410 * 411 * For each parameter, a XML node like this<br> 412 * <code> 413 * <param name="theName">theValue</param> 414 * </code><br> 415 * is generated and appended to the provided parent node.<p> 416 * 417 * @param parentNode the parent node where the parameter nodes are appended to 418 * @param parametersToIgnore if not <code>null</code>, 419 * all parameters in this list are not written to the XML 420 * 421 * @return the parent node 422 */ 423 public Element appendToXml(Element parentNode, List<String> parametersToIgnore) { 424 425 for (Map.Entry<String, Object> entry : m_configurationObjects.entrySet()) { 426 String name = entry.getKey(); 427 // check if the parameter should be ignored 428 if ((parametersToIgnore == null) || !parametersToIgnore.contains(name)) { 429 // now serialize the parameter name and value 430 Object value = entry.getValue(); 431 if (value instanceof List) { 432 @SuppressWarnings("unchecked") 433 List<String> values = (List<String>)value; 434 for (String strValue : values) { 435 // use the original String as value 436 Element paramNode = parentNode.addElement(I_CmsXmlConfiguration.N_PARAM); 437 // set the name attribute 438 paramNode.addAttribute(I_CmsXmlConfiguration.A_NAME, name); 439 // set the text of <param> node 440 paramNode.addText(strValue); 441 } 442 } else { 443 // use the original String as value 444 String strValue = get(name); 445 Element paramNode = parentNode.addElement(I_CmsXmlConfiguration.N_PARAM); 446 // set the name attribute 447 paramNode.addAttribute(I_CmsXmlConfiguration.A_NAME, name); 448 // set the text of <param> node 449 paramNode.addText(strValue); 450 } 451 } 452 } 453 454 return parentNode; 455 } 456 457 /** 458 * @see java.util.Map#clear() 459 */ 460 @Override 461 public void clear() { 462 463 m_configurationStrings.clear(); 464 m_configurationObjects.clear(); 465 } 466 467 /** 468 * @see java.util.Map#containsKey(java.lang.Object) 469 */ 470 @Override 471 public boolean containsKey(Object key) { 472 473 return m_configurationStrings.containsKey(key); 474 } 475 476 /** 477 * @see java.util.Map#containsValue(java.lang.Object) 478 */ 479 @Override 480 public boolean containsValue(Object value) { 481 482 return m_configurationStrings.containsValue(value) || m_configurationObjects.containsValue(value); 483 } 484 485 /** 486 * @see java.util.Map#entrySet() 487 */ 488 @Override 489 public Set<java.util.Map.Entry<String, String>> entrySet() { 490 491 return m_configurationStrings.entrySet(); 492 } 493 494 /** 495 * Returns the String associated with the given parameter.<p> 496 * 497 * @param key the parameter to look up the value for 498 * 499 * @return the String associated with the given parameter 500 */ 501 @Override 502 public String get(Object key) { 503 504 return m_configurationStrings.get(key); 505 } 506 507 /** 508 * Returns the boolean associated with the given parameter, 509 * or the default value in case there is no boolean value for this parameter.<p> 510 * 511 * @param key the parameter to look up the value for 512 * @param defaultValue the default value 513 * 514 * @return the boolean associated with the given parameter, 515 * or the default value in case there is no boolean value for this parameter 516 */ 517 public boolean getBoolean(String key, boolean defaultValue) { 518 519 Object value = m_configurationObjects.get(key); 520 521 if (value instanceof Boolean) { 522 return ((Boolean)value).booleanValue(); 523 524 } else if (value instanceof String) { 525 Boolean b = Boolean.valueOf((String)value); 526 m_configurationObjects.put(key, b); 527 return b.booleanValue(); 528 529 } else { 530 return defaultValue; 531 } 532 } 533 534 /** 535 * Returns the integer associated with the given parameter, 536 * or the default value in case there is no integer value for this parameter.<p> 537 * 538 * @param key the parameter to look up the value for 539 * @param defaultValue the default value 540 * 541 * @return the integer associated with the given parameter, 542 * or the default value in case there is no integer value for this parameter 543 */ 544 public int getInteger(String key, int defaultValue) { 545 546 Object value = m_configurationObjects.get(key); 547 548 if (value instanceof Integer) { 549 return ((Integer)value).intValue(); 550 551 } else if (value instanceof String) { 552 Integer i = new Integer((String)value); 553 m_configurationObjects.put(key, i); 554 return i.intValue(); 555 556 } else { 557 return defaultValue; 558 } 559 } 560 561 /** 562 * Returns the List of Strings associated with the given parameter, 563 * or an empty List in case there is no List of Strings for this parameter.<p> 564 * 565 * The list returned is a copy of the internal data of this object, and as 566 * such you may alter it freely.<p> 567 * 568 * @param key the parameter to look up the value for 569 * 570 * @return the List of Strings associated with the given parameter, 571 * or an empty List in case there is no List of Strings for this parameter 572 */ 573 public List<String> getList(String key) { 574 575 return getList(key, null); 576 } 577 578 /** 579 * Returns the List of Strings associated with the given parameter, 580 * or the default value in case there is no List of Strings for this parameter.<p> 581 * 582 * The list returned is a copy of the internal data of this object, and as 583 * such you may alter it freely.<p> 584 * 585 * @param key the parameter to look up the value for 586 * @param defaultValue the default value 587 * 588 * @return the List of Strings associated with the given parameter, 589 * or the default value in case there is no List of Strings for this parameter 590 */ 591 public List<String> getList(String key, List<String> defaultValue) { 592 593 Object value = m_configurationObjects.get(key); 594 595 if (value instanceof List) { 596 @SuppressWarnings("unchecked") 597 List<String> result = (List<String>)value; 598 return new ArrayList<String>(result); 599 600 } else if (value instanceof String) { 601 List<String> values = new ArrayList<String>(1); 602 values.add((String)value); 603 m_configurationObjects.put(key, values); 604 return values; 605 606 } else { 607 if (defaultValue == null) { 608 return new ArrayList<String>(); 609 } else { 610 return defaultValue; 611 } 612 } 613 } 614 615 /** 616 * Returns the raw Object associated with the given parameter, 617 * or <code>null</code> in case there is no Object for this parameter.<p> 618 * 619 * @param key the parameter to look up the value for 620 * 621 * @return the raw Object associated with the given parameter, 622 * or <code>null</code> in case there is no Object for this parameter.<p> 623 */ 624 public Object getObject(String key) { 625 626 return m_configurationObjects.get(key); 627 } 628 629 /** 630 * Returns the String associated with the given parameter, 631 * or the given default value in case there is no value for this parameter.<p> 632 * 633 * @param key the parameter to look up the value for 634 * @param defaultValue the default value 635 * 636 * @return the String associated with the given parameter, 637 * or the given default value in case there is no value for this parameter.<p> 638 */ 639 public String getString(String key, String defaultValue) { 640 641 String result = get(key); 642 return result == null ? defaultValue : result; 643 } 644 645 /** 646 * @see java.util.Map#hashCode() 647 */ 648 @Override 649 public int hashCode() { 650 651 return m_configurationStrings.hashCode(); 652 } 653 654 /** 655 * @see java.util.Map#keySet() 656 */ 657 @Override 658 public Set<String> keySet() { 659 660 return m_configurationStrings.keySet(); 661 } 662 663 /** 664 * Load the parameters from the given input stream, which must be in property file format.<p> 665 * 666 * @param input the stream to load the input from 667 * 668 * @throws IOException in case of IO errors reading from the stream 669 */ 670 public void load(InputStream input) throws IOException { 671 672 ParameterReader reader = null; 673 674 try { 675 reader = new ParameterReader(new InputStreamReader(input, CmsEncoder.ENCODING_ISO_8859_1)); 676 677 } catch (UnsupportedEncodingException ex) { 678 679 reader = new ParameterReader(new InputStreamReader(input)); 680 } 681 682 while (true) { 683 String line = reader.readParameter(); 684 if (line == null) { 685 return; // EOF 686 } 687 int equalSign = line.indexOf('='); 688 689 if (equalSign > 0) { 690 String key = line.substring(0, equalSign).trim(); 691 String value = line.substring(equalSign + 1).trim(); 692 693 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) { 694 continue; 695 } 696 697 add(key, value, true); 698 } 699 } 700 } 701 702 /** 703 * Set a parameter for this configuration.<p> 704 * 705 * If the parameter already exists then the existing value will be replaced.<p> 706 * 707 * @param key the parameter to set 708 * @param value the value to set 709 * 710 * @return the previous String value from the parameter map 711 */ 712 @Override 713 public String put(String key, String value) { 714 715 String result = remove(key); 716 add(key, value, false); 717 return result; 718 } 719 720 /** 721 * Merges this parameter configuration with the provided other parameter configuration.<p> 722 * 723 * The difference form a simple <code>Map<String, String></code> is that for the parameter 724 * configuration, the values of the keys in both maps are merged and kept in the Object store 725 * as a List.<p> 726 * 727 * As result, <code>this</code> configuration will be altered, the other configuration will 728 * stay unchanged.<p> 729 * 730 * @param other the other parameter configuration to merge this configuration with 731 */ 732 @Override 733 public void putAll(Map<? extends String, ? extends String> other) { 734 735 for (String key : other.keySet()) { 736 boolean tokenize = false; 737 if (other instanceof CmsParameterConfiguration) { 738 Object o = ((CmsParameterConfiguration)other).getObject(key); 739 if (o instanceof List) { 740 tokenize = true; 741 } 742 } 743 add(key, other.get(key), tokenize); 744 } 745 } 746 747 /** 748 * Removes a parameter from this configuration. 749 * 750 * @param key the parameter to remove 751 */ 752 @Override 753 public String remove(Object key) { 754 755 String result = m_configurationStrings.remove(key); 756 m_configurationObjects.remove(key); 757 return result; 758 } 759 760 /** 761 * @see java.util.Map#toString() 762 */ 763 @Override 764 public String toString() { 765 766 return m_configurationStrings.toString(); 767 } 768 769 /** 770 * @see java.util.Map#values() 771 */ 772 @Override 773 public Collection<String> values() { 774 775 return m_configurationStrings.values(); 776 } 777 778 /** 779 * Add a parameter to this configuration.<p> 780 * 781 * If the parameter already exists then the value will be added 782 * to the existing configuration entry and a List will be created for the values.<p> 783 * 784 * @param key the parameter to add 785 * @param value the value to add 786 * @param tokenize decides if a String value should be tokenized or nor 787 */ 788 private void add(String key, String value, boolean tokenize) { 789 790 if (tokenize && (value.indexOf(ParameterTokenizer.COMMA) > 0)) { 791 // token contains commas, so must be split apart then added 792 ParameterTokenizer tokenizer = new ParameterTokenizer(value); 793 while (tokenizer.hasMoreTokens()) { 794 String token = tokenizer.nextToken(); 795 addInternal(key, unescape(token)); 796 } 797 } else if (tokenize) { 798 addInternal(key, unescape(value)); 799 } else { 800 // token contains no commas, so can be simply added 801 addInternal(key, value); 802 } 803 } 804 805 /** 806 * Adds a parameter, parsing the value if required.<p> 807 * 808 * @param key the parameter to add 809 * @param value the value of the parameter 810 */ 811 private void addInternal(String key, String value) { 812 813 Object currentObj = m_configurationObjects.get(key); 814 String currentStr = get(key); 815 816 if (currentObj instanceof String) { 817 // one object already in map - convert it to a list 818 List<String> values = new ArrayList<String>(2); 819 values.add(currentStr); 820 values.add(value); 821 m_configurationObjects.put(key, values); 822 m_configurationStrings.put(key, currentStr + ParameterTokenizer.COMMA + value); 823 } else if (currentObj instanceof List) { 824 // already a list - just add the new token 825 @SuppressWarnings("unchecked") 826 List<String> list = (List<String>)currentObj; 827 list.add(value); 828 m_configurationStrings.put(key, currentStr + ParameterTokenizer.COMMA + value); 829 } else { 830 m_configurationObjects.put(key, value); 831 m_configurationStrings.put(key, value); 832 } 833 } 834 835 /** 836 * Creates a new <tt>Properties</tt> object from the existing configuration 837 * extracting all key-value pars whose key are prefixed 838 * with <tt>keyPrefix</tt>. <p> 839 * 840 * For this example config: 841 * 842 * <pre> 843 * # lines starting with # are comments 844 * db.pool.default.jdbcDriver=net.bull.javamelody.JdbcDriver 845 * db.pool.default.connectionProperties.driver=org.gjt.mm.mysql.Driver 846 * </pre> 847 * 848 * <tt>getPrefixedProperties("db.pool.default.connectionProperties")</tt> 849 * will return a <tt>Properties</tt> object with one single entry: 850 * <pre> 851 * key:"driver", value:"org.gjt.mm.mysql.Driver" 852 * </pre> 853 * 854 * @param keyPrefix prefix to match. If it isn't already, it will be 855 * terminated with a dot. If <tt>null</tt>, it will return 856 * an empty <tt>Properties</tt> instance 857 * @return a new <tt>Properties</tt> object with all the entries from this 858 * configuration whose keys math the prefix 859 */ 860 public Properties getPrefixedProperties(String keyPrefix) { 861 862 Properties props = new Properties(); 863 if (null == keyPrefix) { 864 return props; 865 } 866 867 String dotTerminatedKeyPrefix = keyPrefix + (keyPrefix.endsWith(".") ? "" : "."); 868 for (Map.Entry<String, String> e : entrySet()) { 869 String key = e.getKey(); 870 if ((null != key) && key.startsWith(dotTerminatedKeyPrefix)) { 871 String subKey = key.substring(dotTerminatedKeyPrefix.length()); 872 props.put(subKey, e.getValue()); 873 } 874 } 875 return props; 876 } 877}