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.Shorts.*; 020 021import java.util.*; 022import java.util.concurrent.*; 023import java.util.concurrent.atomic.*; 024 025/** 026 * A writable {@link PropertyStore} 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 a {@link PropertyLookupResult#present(Optional)} result with an empty optional. 042 * This allows you to explicitly override system properties with null values. 043 * Use {@link #unset(String)} if you want to remove a key entirely (so {@link #get(String)} returns {@link PropertyLookupResult#missing()}). 044 * 045 * <h5 class='section'>Example:</h5> 046 * <p class='bjava'> 047 * <jc>// Create a store and add properties</jc> 048 * MapStore <jv>store</jv> = <jk>new</jk> MapStore(); 049 * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>); 050 * <jv>store</jv>.set(<js>"another.property"</js>, <js>"another-value"</js>); 051 * 052 * <jc>// Add to Settings as a source (stores can be used as sources)</jc> 053 * Settings.<jsf>get</jsf>().addSource(<jv>store</jv>); 054 * 055 * <jc>// Override a system property with null</jc> 056 * <jv>store</jv>.set(<js>"system.property"</js>, <jk>null</jk>); 057 * <jc>// get() will now return present(Optional.empty()) for "system.property"</jc> 058 * 059 * <jc>// Remove a property entirely</jc> 060 * <jv>store</jv>.unset(<js>"my.property"</js>); 061 * <jc>// get() will now return missing() for "my.property"</jc> 062 * </p> 063 */ 064public class MapStore implements PropertyStore { 065 066 private final AtomicReference<Map<String,Optional<String>>> map = new AtomicReference<>(); 067 068 /** 069 * Returns a setting from this store. 070 * 071 * <p> 072 * Returns {@link PropertyLookupResult#missing()} if the key doesn't exist in the map, or a present result 073 * (which may contain {@link Optional#empty()} if the value was explicitly set to <c>null</c>). 074 * 075 * @param key The property name. 076 * @return The property lookup result. 077 */ 078 @Override 079 public PropertyLookupResult get(String key) { 080 var m = map.get(); 081 if (m == null) 082 return PropertyLookupResult.missing(); 083 if (!m.containsKey(key)) 084 return PropertyLookupResult.missing(); 085 return PropertyLookupResult.present(m.get(key)); 086 } 087 088 /** 089 * Sets a setting in this store. 090 * 091 * <p> 092 * The internal map is lazily initialized on the first call to this method. Setting a value to <c>null</c> 093 * stores <c>Optional.empty()</c> in the map, which means {@link #get(String)} will return a present result 094 * with an empty optional. This allows you to explicitly override system properties with null values. 095 * 096 * @param key The property name. 097 * @param value The property value, or <c>null</c> to set an empty override. 098 */ 099 @Override 100 public void set(String key, String value) { 101 var m = map.get(); 102 if (m == null) { 103 var newMap = new ConcurrentHashMap<String,Optional<String>>(); 104 m = map.compareAndSet(null, newMap) ? newMap : map.get(); // Not easily testable. 105 } 106 m.put(key, o(value)); 107 } 108 109 /** 110 * Clears all entries from this store. 111 * 112 * <p> 113 * After calling this method, all keys will be removed from the map, and {@link #get(String)} will return 114 * {@link PropertyLookupResult#missing()} for all keys. 115 */ 116 @Override 117 public void clear() { 118 var m = map.get(); 119 if (m != null) 120 m.clear(); 121 } 122 123 /** 124 * Removes a setting from this store. 125 * 126 * <p> 127 * After calling this method, {@link #get(String)} will return {@link PropertyLookupResult#missing()} for 128 * the specified key, indicating that the key doesn't exist in this store. 129 * 130 * @param name The property name to remove. 131 */ 132 @Override 133 public void unset(String name) { 134 var m = map.get(); 135 if (m != null) 136 m.remove(name); 137 } 138 139}