001/* 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * Portions Copyrighted 2010-2016 ForgeRock AS. 026 */ 027package com.iplanet.am.util; 028 029import static org.forgerock.openam.utils.CollectionUtils.asSet; 030 031import com.iplanet.sso.SSOToken; 032import com.sun.identity.common.AttributeStruct; 033import com.sun.identity.common.PropertiesFinder; 034import com.sun.identity.common.configuration.ConfigurationListener; 035import com.sun.identity.common.configuration.ConfigurationObserver; 036import com.sun.identity.common.configuration.ServerConfiguration; 037import com.sun.identity.security.AdminTokenAction; 038import com.sun.identity.shared.Constants; 039import com.sun.identity.sm.SMSEntry; 040 041import java.io.FileInputStream; 042import java.io.IOException; 043import java.io.PrintWriter; 044import java.io.StringWriter; 045import java.net.InetAddress; 046import java.security.AccessController; 047import java.util.Collections; 048import java.util.HashMap; 049import java.util.HashSet; 050import java.util.Map; 051import java.util.MissingResourceException; 052import java.util.Properties; 053import java.util.ResourceBundle; 054import java.util.Set; 055import java.util.concurrent.atomic.AtomicReference; 056import javax.annotation.Nullable; 057 058import org.forgerock.guava.common.base.Predicate; 059import org.forgerock.guava.common.collect.ImmutableMap; 060import org.forgerock.guava.common.collect.Maps; 061import org.forgerock.openam.cts.api.CoreTokenConstants; 062import org.forgerock.openam.utils.StringUtils; 063 064/** 065 * This class provides functionality that allows single-point-of-access to all 066 * related system properties. 067 * <p> 068 * The system properties can be set in couple of ways: programmatically by 069 * calling the <code>initializeProperties</code> method, or can be statically 070 * loaded at startup from a file named: 071 * <code>AMConfig.[class,properties]</code>. 072 * Setting the properties through the API takes precedence and will replace the 073 * properties loaded via file. For statically loading the properties via a file, 074 * this class tries to first find a class, <code>AMConfig.class</code>, and 075 * then a file, <code>AMConfig.properties</code> in the CLASSPATH accessible 076 * to this code. The <code>AMConfig.class</code> takes precedence over the 077 * flat file <code>AMConfig.properties</code>. 078 * <p> 079 * If multiple servers are running, each may have their own configuration file. 080 * The naming convention for such scenarios is 081 * <code>AMConfig-<serverName></code>. 082 * @supported.all.api 083 */ 084public class SystemProperties { 085 086 /** 087 * Runtime flag to be set, in order to override the path of the 088 * configuration file. 089 */ 090 public static final String CONFIG_PATH = "com.iplanet.services.configpath"; 091 092 /** 093 * Default name of the configuration file. 094 */ 095 public static final String CONFIG_FILE_NAME = "serverconfig.xml"; 096 097 /** 098 * New configuration file extension 099 */ 100 public static final String PROPERTIES = "properties"; 101 102 public static final String NEWCONFDIR = "NEW_CONF_DIR"; 103 104 private static final String SERVER_NAME_PROPERTY = "server.name"; 105 106 private static final String CONFIG_NAME_PROPERTY = "amconfig"; 107 108 private static final String AMCONFIG_FILE_NAME = "AMConfig"; 109 110 /** Regular expression pattern for a sequence of 1 or more white space characters. */ 111 private static final String WHITESPACE = "\\s+"; 112 113 private static final Map<String, AttributeStruct> ATTRIBUTE_MAP = initAttributeMapping(); 114 115 private static final int TAG_START = '%'; 116 117 /** 118 * Maps from tags to the system properties that they should be replaced with. System property values containing 119 * these tags will be replaced with the actual values of these properties by {@link #get(String)}. 120 */ 121 private static final Map<String, String> TAG_SWAP_PROPERTIES = ImmutableMap.<String, String>builder() 122 .put("%SERVER_PORT%", Constants.AM_SERVER_PORT) 123 .put("%SERVER_URI%", Constants.AM_SERVICES_DEPLOYMENT_DESCRIPTOR) 124 .put("%SERVER_HOST%", Constants.AM_SERVER_HOST) 125 .put("%SERVER_PROTO%", Constants.AM_SERVER_PROTOCOL) 126 .put("%BASE_DIR%", CONFIG_PATH) 127 .put("%SESSION_ROOT_SUFFIX%", CoreTokenConstants.SYS_PROPERTY_SESSION_HA_REPOSITORY_ROOT_SUFFIX) 128 .put("%SESSION_STORE_TYPE%", CoreTokenConstants.SYS_PROPERTY_SESSION_HA_REPOSITORY_TYPE) 129 .build(); 130 131 private static final boolean SITEMONITOR_DISABLED; 132 133 /** 134 * Reference to the current properties map and tagswap values. 135 */ 136 private static final AtomicReference<PropertiesHolder> propertiesHolderRef = 137 new AtomicReference<>(new PropertiesHolder()); 138 139 private static String initError = null; 140 private static String initSecondaryError = null; 141 private static String instanceName = null; 142 143 /* 144 * Initialization to load the properties file for config information before 145 * anything else starts. 146 */ 147 static { 148 try { 149 // Load properties from file 150 String serverName = System.getProperty(SERVER_NAME_PROPERTY); 151 String configName = System.getProperty(CONFIG_NAME_PROPERTY, AMCONFIG_FILE_NAME); 152 String fname = null; 153 if (serverName != null) { 154 serverName = serverName.replace('.', '_'); 155 fname = configName + "-" + serverName; 156 } else { 157 fname = configName; 158 } 159 initializeProperties(fname); 160 PropertiesHolder props = propertiesHolderRef.get(); 161 162 // Get the location of the new configuration file in case 163 // of single war deployment 164 try { 165 String newConfigFileLoc = props.getProperty(Constants.AM_NEW_CONFIGFILE_PATH); 166 167 if (!StringUtils.isEmpty(newConfigFileLoc) && !NEWCONFDIR.equals(newConfigFileLoc)) { 168 String hostName = InetAddress.getLocalHost().getHostName().toLowerCase(); 169 String serverURI = props.getProperty(Constants.AM_SERVICES_DEPLOYMENT_DESCRIPTOR).replace('/', '_') 170 .toLowerCase(); 171 172 String fileName = newConfigFileLoc + "/" + AMCONFIG_FILE_NAME + serverURI + hostName + 173 props.getProperty(Constants.AM_SERVER_PORT) + "." + PROPERTIES; 174 175 try { 176 props = loadProperties(props, fileName); 177 } catch (IOException ioe) { 178 try { 179 props = loadProperties(props, newConfigFileLoc + "/" + AMCONFIG_FILE_NAME + "." + 180 PROPERTIES); 181 } catch (IOException ioe2) { 182 saveException(ioe2); 183 } 184 } 185 propertiesHolderRef.set(props); 186 } 187 } catch (Exception ex) { 188 saveException(ex); 189 } 190 } catch (MissingResourceException e) { 191 // Can't print the message to debug due to dependency 192 // Save it as a String and provide when requested. 193 StringWriter sw = new StringWriter(); 194 e.printStackTrace(new PrintWriter(sw)); 195 initError = sw.toString(); 196 } 197 SITEMONITOR_DISABLED = Boolean.parseBoolean(getProp(Constants.SITEMONITOR_DISABLED, "false")); 198 } 199 200 private static PropertiesHolder loadProperties(PropertiesHolder props, String file) throws IOException { 201 try (FileInputStream fis = new FileInputStream(file)) { 202 Properties temp = new Properties(); 203 temp.load(fis); 204 return props.putAll(temp); 205 } 206 } 207 208 /** 209 * Helper function to handle associated exceptions during initialization of 210 * properties using external properties file in a single war deployment. 211 */ 212 static void saveException(Exception ex) { 213 // Save it as a String and provide when requested. 214 StringWriter sw = new StringWriter(); 215 ex.printStackTrace(new PrintWriter(sw)); 216 initSecondaryError = sw.toString(); 217 } 218 219 /** 220 * This method lets you query for a system property whose value is same as 221 * <code>String</code> key. The method first tries to read the property 222 * from java.lang.System followed by a lookup in the config file. 223 * 224 * @param key 225 * type <code>String</code>, the key whose value one is 226 * looking for. 227 * @return the value if the key exists; otherwise returns <code>null</code> 228 */ 229 public static String get(String key) { 230 231 String answer = null; 232 233 // look up values in SMS services only if in server mode. 234 if (isServerMode() || SITEMONITOR_DISABLED) { 235 AttributeStruct ast = ATTRIBUTE_MAP.get(key); 236 if (ast != null) { 237 answer = PropertiesFinder.getProperty(key, ast); 238 } 239 } 240 241 if (answer == null) { 242 answer = getProp(key); 243 244 final Map<String, String> tagswapValues = propertiesHolderRef.get().tagSwapValues; 245 if (answer != null && tagswapValues != null && answer.indexOf(TAG_START) != -1) { 246 for (Map.Entry<String, String> tagSwapEntry : tagswapValues.entrySet()) { 247 String k = tagSwapEntry.getKey(); 248 String val = tagSwapEntry.getValue(); 249 250 if (k.equals("%SERVER_URI%")) { 251 if (!StringUtils.isEmpty(val)) { 252 if (val.charAt(0) == '/') { 253 answer = answer.replaceAll("/%SERVER_URI%", val); 254 String lessSlash = val.substring(1); 255 answer = answer.replaceAll("%SERVER_URI%", lessSlash); 256 } else { 257 answer = answer.replaceAll(k, val); 258 } 259 } 260 } else { 261 answer = answer.replaceAll(k, val); 262 } 263 } 264 265 if (answer.contains("%ROOT_SUFFIX%")) { 266 answer = answer.replaceAll("%ROOT_SUFFIX%", SMSEntry.getAMSdkBaseDN()); 267 } 268 } 269 } 270 271 return answer; 272 } 273 274 private static String getProp(String key, String def) { 275 String value = getProp(key); 276 return ((value == null) ? def : value); 277 } 278 279 private static String getProp(String key) { 280 String answer = System.getProperty(key); 281 if (answer == null) { 282 answer = propertiesHolderRef.get().getProperty(key); 283 } 284 return answer; 285 } 286 287 /** 288 * This method lets you query for a system property whose value is same as 289 * <code>String</code> key. 290 * 291 * @param key the key whose value one is looking for. 292 * @param def the default value if the key does not exist. 293 * @return the value if the key exists; otherwise returns default value. 294 */ 295 public static String get(String key, String def) { 296 String value = get(key); 297 return value == null ? def : value; 298 } 299 300 /** 301 * Returns the property value as a boolean 302 * 303 * @param key the key whose value one is looking for. 304 * @return the boolean value if the key exists; otherwise returns false 305 */ 306 public static boolean getAsBoolean(String key) { 307 String value = get(key); 308 return Boolean.parseBoolean(value); 309 } 310 311 /** 312 * Returns the property value as a boolean 313 * 314 * @param key the property name. 315 * @param defaultValue value if key is not found. 316 * @return the boolean value if the key exists; otherwise the default value 317 */ 318 public static boolean getAsBoolean(String key, boolean defaultValue) { 319 String value = get(key); 320 321 if (value == null) { 322 return defaultValue; 323 } 324 325 return Boolean.parseBoolean(value); 326 } 327 328 /** 329 * @param key The System Property key to lookup. 330 * @param defaultValue If the property was not set, or could not be parsed to an int. 331 * @return Either the defaultValue, or the numeric value assigned to the System Property. 332 */ 333 public static int getAsInt(String key, int defaultValue) { 334 String value = get(key); 335 336 if (value == null) { 337 return defaultValue; 338 } 339 try { 340 return Integer.parseInt(value); 341 } catch (NumberFormatException e) { 342 return defaultValue; 343 } 344 } 345 346 /** 347 * @param key The System Property key to lookup. 348 * @param defaultValue If the property was not set, or could not be parsed to a long. 349 * @return Either the defaultValue, or the numeric value assigned to the System Property. 350 */ 351 public static long getAsLong(String key, long defaultValue) { 352 String value = get(key); 353 354 if (value == null) { 355 return defaultValue; 356 } 357 try { 358 return Long.parseLong(value); 359 } catch (NumberFormatException e) { 360 return defaultValue; 361 } 362 } 363 364 /** 365 * Parses a system property as a set of strings by splitting the value on the given delimiter expression. 366 * 367 * @param key The System Property key to lookup. 368 * @param delimiterRegex The regular expression to use to split the value into elements in the set. 369 * @param defaultValue The default set to return if the property does not exist. 370 * @return the value of the property parsed as a set of strings. 371 */ 372 public static Set<String> getAsSet(String key, String delimiterRegex, Set<String> defaultValue) { 373 String value = get(key); 374 if (value == null || value.trim().isEmpty()) { 375 return defaultValue; 376 } 377 return asSet(value.split(delimiterRegex)); 378 } 379 380 /** 381 * Parses a system property as a set of strings by splitting the value on the given delimiter expression. 382 * 383 * @param key The System Property key to lookup. 384 * @param delimiterRegex The regular expression to use to split the value into elements in the set. 385 * @return the value of the property parsed as a set of strings or an empty set if no match is found. 386 */ 387 public static Set<String> getAsSet(String key, String delimiterRegex) { 388 return getAsSet(key, delimiterRegex, Collections.<String>emptySet()); 389 } 390 391 /** 392 * Parses a system property as a set of strings by splitting the value on white space characters. 393 * 394 * @param key The System Property key to lookup. 395 * @return the value of the property parsed as a set of strings or an empty set if no match is found. 396 */ 397 public static Set<String> getAsSet(String key) { 398 return getAsSet(key, WHITESPACE); 399 } 400 401 /** 402 * Returns all the properties defined and their values. This is a defensive copy of the properties and so updates 403 * to the returned object will not be reflected in the actual properties used by OpenAM. 404 * 405 * @return Properties object with a copy of all the key value pairs. 406 */ 407 public static Properties getProperties() { 408 Properties properties = new Properties(); 409 properties.putAll(propertiesHolderRef.get().properties); 410 return properties; 411 } 412 413 /** 414 * This method lets you get all the properties defined and their values. The 415 * method first tries to load the properties from java.lang.System followed 416 * by a lookup in the config file. 417 * 418 * @return Properties object with all the key value pairs. 419 * 420 */ 421 public static Properties getAll() { 422 Properties properties = new Properties(); 423 properties.putAll(propertiesHolderRef.get().properties); 424 // Iterate over the System Properties & add them in result obj 425 properties.putAll(System.getProperties()); 426 return properties; 427 } 428 429 /** 430 * This method lets you query for all the platform properties defined and 431 * their values. Returns a Properties object with all the key value pairs. 432 * 433 * @deprecated use <code>getAll()</code> 434 * 435 * @return the platform properties 436 */ 437 public static Properties getPlatform() { 438 return getAll(); 439 } 440 441 /** 442 * Initializes properties bundle from the <code>file<code> 443 * passed. 444 * 445 * @param file type <code>String</code>, file name for the resource bundle 446 * @exception MissingResourceException 447 */ 448 public static void initializeProperties(String file) throws MissingResourceException { 449 Properties props = new Properties(); 450 ResourceBundle bundle = ResourceBundle.getBundle(file); 451 // Copy the properties to props 452 for (String key : bundle.keySet()) { 453 props.put(key, bundle.getString(key)); 454 } 455 initializeProperties(props, false, false); 456 } 457 458 public static void initializeProperties(Properties properties) { 459 initializeProperties(properties, false); 460 } 461 462 /** 463 * Initializes the properties to be used by OpenAM. Ideally this 464 * must be called first before any other method is called within OpenAM. 465 * This method provides a programmatic way to set the properties, and will 466 * override similar properties if loaded for a properties file. 467 * 468 * @param properties properties for OpenAM 469 * @param reset <code>true</code> to reset existing properties. 470 */ 471 public static void initializeProperties(Properties properties, boolean reset) { 472 initializeProperties(properties, reset, false); 473 } 474 475 /** 476 * Initializes the properties to be used by OpenAM. Ideally this 477 * must be called first before any other method is called within OpenAM. 478 * This method provides a programmatic way to set the properties, and will 479 * override similar properties if loaded for a properties file. 480 * 481 * @param properties properties for OpenAM. 482 * @param reset <code>true</code> to reset existing properties. 483 * @param withDefaults <code>true</code> to include default properties. 484 */ 485 public static void initializeProperties(Properties properties, boolean reset, boolean withDefaults) { 486 Properties defaultProp = null; 487 if (withDefaults) { 488 SSOToken appToken = AccessController.doPrivileged(AdminTokenAction.getInstance()); 489 defaultProp = ServerConfiguration.getDefaults(appToken); 490 } 491 492 PropertiesHolder oldProps; 493 PropertiesHolder newProps; 494 do { 495 oldProps = propertiesHolderRef.get(); 496 final Properties combined = new Properties(); 497 if (defaultProp != null) { 498 combined.putAll(defaultProp); 499 } 500 501 if (!reset) { 502 combined.putAll(oldProps.properties); 503 } 504 505 combined.putAll(properties); 506 507 newProps = new PropertiesHolder(Maps.fromProperties(combined)); 508 } while (!propertiesHolderRef.compareAndSet(oldProps, newProps)); 509 } 510 511 /** 512 * Initializes a property to be used by OpenAM. Ideally this 513 * must be called first before any other method is called within OpenAM. 514 * This method provides a programmatic way to set a specific property, and 515 * will override similar property if loaded for a properties file. 516 * 517 * @param propertyName property name. 518 * @param propertyValue property value. 519 */ 520 public static void initializeProperties(String propertyName, String propertyValue) { 521 Properties newProps = new Properties(); 522 newProps.put(propertyName, propertyValue); 523 initializeProperties(newProps, false, false); 524 } 525 526 /** 527 * Returns a counter for last modification. The counter is incremented if 528 * the properties are changed by calling the following method 529 * <code>initializeProperties</code>. This is a convenience method for 530 * applications to track changes to OpenAM properties. 531 * 532 * @return counter of the last modification 533 */ 534 public static long lastModified() { 535 return propertiesHolderRef.get().lastModified; 536 } 537 538 /** 539 * Returns error messages during initialization, else <code>null</code>. 540 * 541 * @return error messages during initialization 542 */ 543 public static String getInitializationError() { 544 return initError; 545 } 546 547 /** 548 * Returns error messages during initialization using the single war 549 * deployment, else <code>null</code>. 550 * 551 * @return error messages during initialization of OpenAM as single war 552 */ 553 public static String getSecondaryInitializationError() { 554 return initSecondaryError; 555 } 556 557 /** 558 * Sets the server instance name of which properties are retrieved 559 * to initialized this object. 560 * 561 * @param name Server instance name. 562 */ 563 public static void setServerInstanceName(String name) { 564 instanceName = name; 565 } 566 567 /** 568 * Returns the server instance name of which properties are retrieved 569 * to initialized this object. 570 * 571 * @return Server instance name. 572 */ 573 public static String getServerInstanceName() { 574 return instanceName; 575 } 576 577 /** 578 * Returns <code>true</code> if instance is running in server mode. 579 * 580 * @return <code>true</code> if instance is running in server mode. 581 */ 582 public static boolean isServerMode() { 583 return IsServerModeHolder.isServerMode; 584 } 585 586 /** 587 * Returns the property name to service attribute schema name mapping. 588 * 589 * @return Property name to service attribute schema name mapping. 590 */ 591 public static Map getAttributeMap() { 592 return ATTRIBUTE_MAP; 593 } 594 595 private static Map<String, AttributeStruct> initAttributeMapping() { 596 final Map<String, AttributeStruct> attributeMapping = new HashMap<>(); 597 try { 598 ResourceBundle rb = ResourceBundle.getBundle("serverAttributeMap"); 599 for (String propertyName : rb.keySet()) { 600 attributeMapping.put(propertyName, new AttributeStruct(rb.getString(propertyName))); 601 } 602 } catch (MissingResourceException mse) { 603 // No Resource Bundle Found, Continue. 604 // Could be in Test Mode. 605 } 606 return Collections.unmodifiableMap(attributeMapping); 607 } 608 609 610 /** 611 * Lazy initialisation holder idiom for server mode flag as this is read frequently but never changes. 612 */ 613 private static final class IsServerModeHolder { 614 // use getProp and not get method to avoid infinite loop 615 private static final boolean isServerMode = Boolean.parseBoolean(getProp(Constants.SERVER_MODE, "false")); 616 } 617 618 /** 619 * A singleton enum for the configuration listeners, which will be lazily initialized on first use. The code 620 * here cannot be added to the {@code SystemProperties} class initialization as it would create a cyclic 621 * dependency on the static initialization of {@code ConfigurationObserver}. 622 */ 623 private enum Listeners { 624 INSTANCE; 625 626 private final Map<String, ServicePropertiesConfigurationListener> servicePropertiesListeners; 627 private final ServicePropertiesConfigurationListener platformServicePropertiesListener; 628 629 Listeners() { 630 servicePropertiesListeners = new HashMap<>(); 631 platformServicePropertiesListener = new ServicePropertiesConfigurationListener(); 632 633 Map<String, Set<String>> services = new HashMap<>(); 634 for (Map.Entry<String, AttributeStruct> property : ATTRIBUTE_MAP.entrySet()) { 635 String serviceName = property.getValue().getServiceName(); 636 if (!services.containsKey(serviceName)) { 637 services.put(serviceName, new HashSet<String>()); 638 } 639 services.get(serviceName).add(property.getKey()); 640 } 641 642 ConfigurationObserver configurationObserver = ConfigurationObserver.getInstance(); 643 644 for (final Map.Entry<String, Set<String>> service : services.entrySet()) { 645 if (!Constants.SVC_NAME_PLATFORM.equals(service.getKey())) { 646 Set<String> properties = service.getValue(); 647 ServicePropertiesConfigurationListener listener = 648 new ServicePropertiesConfigurationListener(properties); 649 for (String property : properties) { 650 servicePropertiesListeners.put(property, listener); 651 } 652 configurationObserver.addServiceListener(listener, new Predicate<String>() { 653 @Override 654 public boolean apply(@Nullable String s) { 655 return s != null && s.equals(service.getKey()); 656 } 657 }); 658 } 659 } 660 661 configurationObserver.addServiceListener(platformServicePropertiesListener, new Predicate<String>() { 662 @Override 663 public boolean apply(@Nullable String s) { 664 return Constants.SVC_NAME_PLATFORM.equals(s); 665 } 666 }); 667 } 668 } 669 670 /** 671 * A listener for the properties that are provided by a single service. Property values are cached so that 672 * property listeners are only notified when the property(-ies) they are observing have changed. 673 */ 674 private static final class ServicePropertiesConfigurationListener implements ConfigurationListener { 675 676 private final Map<String, Set<ConfigurationListener>> propertyListeners = new HashMap<>(); 677 private final Map<String, String> propertyValues = new HashMap<>(); 678 679 private ServicePropertiesConfigurationListener(Set<String> propertyNames) { 680 for (String propertyName : propertyNames) { 681 registerPropertyName(propertyName); 682 } 683 } 684 685 private void registerPropertyName(String propertyName) { 686 propertyListeners.put(propertyName, Collections.synchronizedSet(new HashSet<ConfigurationListener>())); 687 } 688 689 private ServicePropertiesConfigurationListener() { 690 // nothing to see here 691 } 692 693 @Override 694 public void notifyChanges() { 695 Set<ConfigurationListener> affectedListeners = new HashSet<>(); 696 for (Map.Entry<String, Set<ConfigurationListener>> propertyListeners : this.propertyListeners.entrySet()) { 697 String propertyName = propertyListeners.getKey(); 698 String value = get(propertyName); 699 String oldValue = propertyValues.get(propertyName); 700 if (value != null && !value.equals(oldValue) || value == null && oldValue != null) { 701 Set<ConfigurationListener> listeners = propertyListeners.getValue(); 702 for (ConfigurationListener listener : listeners) { 703 affectedListeners.add(listener); 704 } 705 propertyValues.put(propertyName, value); 706 } 707 } 708 for (ConfigurationListener listener : affectedListeners) { 709 listener.notifyChanges(); 710 } 711 } 712 } 713 714 /** 715 * Listen for runtime changes to a system property value. Only values that are stored in the SMS will 716 * be changed at runtime. See {@code serverdefaults.properties}, {@code amPlatform.xml} and 717 * {@code serverAttributeMap.properties}. 718 * 719 * @param listener The listener to call when one of the provided properties has changed. 720 * @param properties The list of properties that should be observed. A change in any one of these properties 721 * will cause the listener to be notified. 722 */ 723 public static void observe(ConfigurationListener listener, String... properties) { 724 for (String property : properties) { 725 ServicePropertiesConfigurationListener serviceListener = 726 Listeners.INSTANCE.servicePropertiesListeners.get(property); 727 if (serviceListener == null) { 728 serviceListener = Listeners.INSTANCE.platformServicePropertiesListener; 729 } 730 synchronized (serviceListener) { 731 if (!serviceListener.propertyListeners.containsKey(property)) { 732 serviceListener.registerPropertyName(property); 733 } 734 serviceListener.propertyListeners.get(property).add(listener); 735 } 736 } 737 } 738 739 /** 740 * Holds the current properties map together with the tagswap values and last updated timestamp to allow atomic 741 * updates of all three as one unit without locking. This is an immutable structure that is intended to be used 742 * with an AtomicReference. 743 */ 744 private static final class PropertiesHolder { 745 private final Map<String, String> properties; 746 private final Map<String, String> tagSwapValues; 747 private final long lastModified; 748 749 PropertiesHolder() { 750 this.properties = Collections.emptyMap(); 751 this.tagSwapValues = Collections.emptyMap(); 752 this.lastModified = System.currentTimeMillis(); 753 } 754 755 PropertiesHolder(final Map<String, String> properties) { 756 Map<String, String> tagSwapMap = new HashMap<>(); 757 for (Map.Entry<String, String> tagSwapEntry : TAG_SWAP_PROPERTIES.entrySet()) { 758 String tag = tagSwapEntry.getKey(); 759 String propertyName = tagSwapEntry.getValue(); 760 String val = System.getProperty(propertyName); 761 if (val == null) { 762 val = properties.get(propertyName); 763 } 764 tagSwapMap.put(tag, val); 765 } 766 this.properties = Collections.unmodifiableMap(properties); 767 this.tagSwapValues = Collections.unmodifiableMap(tagSwapMap); 768 this.lastModified = System.currentTimeMillis(); 769 } 770 771 String getProperty(String name) { 772 return properties.get(name); 773 } 774 775 PropertiesHolder putAll(Properties newProperties) { 776 return new PropertiesHolder(Maps.fromProperties(newProperties)); 777 } 778 } 779}