001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.settings; 018 019import static org.apache.juneau.commons.utils.Utils.*; 020 021import java.util.*; 022import java.util.concurrent.*; 023import java.util.concurrent.atomic.*; 024 025/** 026 * A writable {@link SettingStore} implementation backed by a thread-safe map. 027 * 028 * <p> 029 * This class provides a mutable store for settings that can be modified at runtime. It's particularly useful 030 * for creating custom property stores (e.g., Spring properties, configuration files) that can be added to 031 * {@link Settings} as sources. 032 * 033 * <h5 class='section'>Thread Safety:</h5> 034 * <p> 035 * This class is thread-safe. The internal map is lazily initialized using {@link AtomicReference} and 036 * {@link ConcurrentHashMap} for thread-safe operations. 037 * 038 * <h5 class='section'>Null Value Handling:</h5> 039 * <p> 040 * Setting a value to <c>null</c> stores <c>Optional.empty()</c> in the map, which means {@link #get(String)} 041 * will return <c>Optional.empty()</c> (not <c>null</c>). This allows you to explicitly override system properties 042 * with null values. Use {@link #unset(String)} if you want to remove a key entirely (so {@link #get(String)} returns <c>null</c>). 043 * 044 * <h5 class='section'>Example:</h5> 045 * <p class='bjava'> 046 * <jc>// Create a store and add properties</jc> 047 * MapStore <jv>store</jv> = <jk>new</jk> MapStore(); 048 * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>); 049 * <jv>store</jv>.set(<js>"another.property"</js>, <js>"another-value"</js>); 050 * 051 * <jc>// Add to Settings as a source (stores can be used as sources)</jc> 052 * Settings.<jsf>get</jsf>().addSource(<jv>store</jv>); 053 * 054 * <jc>// Override a system property with null</jc> 055 * <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>); 056 * <jc>// get() will now return Optional.empty() for "system.property"</jc> 057 * 058 * <jc>// Remove a property entirely</jc> 059 * <jv>store</jv>.unset(<js>"my.property"</js>); 060 * <jc>// get() will now return null for "my.property"</jc> 061 * </p> 062 */ 063public class MapStore implements SettingStore { 064 065 private final AtomicReference<Map<String,Optional<String>>> map = new AtomicReference<>(); 066 067 /** 068 * Returns a setting from this store. 069 * 070 * <p> 071 * Returns <c>null</c> if the key doesn't exist in the map, or the stored value (which may be 072 * <c>Optional.empty()</c> if the value was explicitly set to <c>null</c>). 073 * 074 * @param key The property name. 075 * @return The property value, <c>null</c> if the key doesn't exist, or <c>Optional.empty()</c> if the key 076 * exists but has a null value. 077 */ 078 @Override 079 public Optional<String> get(String key) { 080 var m = map.get(); 081 if (m == null) 082 return null; 083 return m.get(key); 084 } 085 086 /** 087 * Sets a setting in this store. 088 * 089 * <p> 090 * The internal map is lazily initialized on the first call to this method. Setting a value to <c>null</c> 091 * stores <c>Optional.empty()</c> in the map, which means {@link #get(String)} will return <c>Optional.empty()</c> 092 * (not <c>null</c>). This allows you to explicitly override system properties with null values. 093 * 094 * @param key The property name. 095 * @param value The property value, or <c>null</c> to set an empty override. 096 */ 097 @Override 098 public void set(String key, String value) { 099 var m = map.get(); 100 if (m == null) { 101 var newMap = new ConcurrentHashMap<String,Optional<String>>(); 102 m = map.compareAndSet(null, newMap) ? newMap : map.get(); // Not easily testable. 103 } 104 m.put(key, opt(value)); 105 } 106 107 /** 108 * Clears all entries from this store. 109 * 110 * <p> 111 * After calling this method, all keys will be removed from the map, and {@link #get(String)} will return 112 * <c>null</c> for all keys. 113 */ 114 @Override 115 public void clear() { 116 var m = map.get(); 117 if (m != null) 118 m.clear(); 119 } 120 121 /** 122 * Removes a setting from this store. 123 * 124 * <p> 125 * After calling this method, {@link #get(String)} will return <c>null</c> for the specified key, 126 * indicating that the key doesn't exist in this store (as opposed to returning <c>Optional.empty()</c>, 127 * which would indicate the key exists but has a null value). 128 * 129 * @param name The property name to remove. 130 */ 131 @Override 132 public void unset(String name) { 133 var m = map.get(); 134 if (m != null) 135 m.remove(name); 136 } 137 138}