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