From 097f51a0df1ef575107eb5486d790e7cf20e7ff4 Mon Sep 17 00:00:00 2001 From: Chris Vest Date: Wed, 18 Oct 2017 10:53:26 +0200 Subject: [PATCH] Make it possible to register dynamic update listeners with Config Such that modules can be notified when changes are made to settings they are interested in. --- .../neo4j/kernel/configuration/Config.java | 31 ++++++++++++++++--- .../kernel/configuration/ConfigTest.java | 16 ++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java b/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java index 4b00870c6935a..f2be3b19f5e5b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java @@ -31,6 +31,9 @@ import java.util.Optional; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -55,6 +58,7 @@ import org.neo4j.logging.Log; import org.neo4j.logging.Logger; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonMap; import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.kernel.configuration.Connector.ConnectorType.BOLT; @@ -77,6 +81,7 @@ public class Config implements DiagnosticsProvider, Configuration private final List configOptions; private final Map params = new CopyOnWriteHashMap<>(); // Read heavy workload + private final Map>> updateListeners = new ConcurrentHashMap<>(); private final ConfigurationMigrator migrator; private final List validators = new ArrayList<>(); private final Map overriddenDefaults = new CopyOnWriteHashMap<>(); @@ -571,11 +576,12 @@ public Optional getValue( @Nonnull String key ) * @implNote No migration or config validation is done. If you need this you have to refactor this method. * * @param setting The setting to set to the specified value. - * @param newValue The new value to set, passing {@code null} or empty should reset the value back to default value. + * @param update The new value to set, passing {@code null} or the empty string should reset the value back to default value. * @throws IllegalArgumentException if the provided setting is unknown or not dynamic. * @throws InvalidSettingException if the value is not formatted correctly. */ - public void updateDynamicSetting( String setting, String newValue ) throws IllegalArgumentException, InvalidSettingException + public void updateDynamicSetting( String setting, String update ) + throws IllegalArgumentException, InvalidSettingException { // Make sure the setting is valid and is marked as dynamic Optional option = findConfigValue( setting ); @@ -592,8 +598,9 @@ public void updateDynamicSetting( String setting, String newValue ) throws Illeg } String oldValue; + String newValue; - if ( newValue == null || newValue.isEmpty() ) + if ( update == null || update.isEmpty() ) { // Empty means we want to delete the configured value and fallback to the default value boolean hasDefault = overriddenDefaults.containsKey( setting ); @@ -603,7 +610,7 @@ public void updateDynamicSetting( String setting, String newValue ) throws Illeg else { // Change setting, make sure it's valid - Map newEntry = stringMap( setting, newValue ); + Map newEntry = stringMap( setting, update ); List settingValidators = configOptions.stream() .map( ConfigOptions::settingGroup ) .collect( Collectors.toList() ); @@ -615,10 +622,12 @@ public void updateDynamicSetting( String setting, String newValue ) throws Illeg synchronized ( params ) { oldValue = getDefaultValueOf( setting ); - params.put( setting, newValue ); + params.put( setting, update ); } + newValue = update; } log.info( "Setting changed: '%s' changed from '%s' to '%s'", setting, oldValue, newValue ); + updateListeners.getOrDefault( setting, emptyList() ).forEach( l -> l.accept( oldValue, newValue ) ); } private String getDefaultValueOf( String setting ) @@ -632,6 +641,18 @@ private Optional findConfigValue( String setting ) .filter( it -> it.name().equals( setting ) ).findFirst(); } + public void registerDynamicUpdateListener( Setting setting, BiConsumer listener ) + { + String key = setting.name(); + BiConsumer projectedListener = ( oldValStr, newValStr ) -> + { + V oldVal = setting.apply( s -> oldValStr ); + V newVal = setting.apply( s -> newValStr ); + listener.accept( oldVal, newVal ); + }; + updateListeners.computeIfAbsent( key, k -> new ConcurrentLinkedQueue<>() ).add( projectedListener ); + } + /** * @return all effective config values */ diff --git a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java b/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java index 04ea5ad1e27bf..71dcef1a09668 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nonnull; import org.neo4j.configuration.DocumentedDefaultValue; @@ -388,4 +389,19 @@ public void updateDynamicShouldLogChanges() throws Exception order.verify( log ).info( changedMessage, settingName, "true", "true" ); verifyNoMoreInteractions( log ); } + + @Test + public void updateDynamicShouldInformRegisteredCallbacks() throws Exception + { + Config config = Config.builder().withConfigClasses( singletonList( new MyDynamicSettings() ) ).build(); + AtomicInteger counter = new AtomicInteger( 0 ); + config.registerDynamicUpdateListener( MyDynamicSettings.boolSetting, ( previous, update ) -> + { + counter.getAndIncrement(); + assertTrue( previous ); + assertFalse( update ); + } ); + config.updateDynamicSetting( MyDynamicSettings.boolSetting.name(), "false" ); + assertThat( counter.get(), is( 1 ) ); + } }