All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.config.ConfigRegistryImpl Maven / Gradle / Ivy

package io.scalecube.config;

import io.scalecube.config.audit.ConfigEvent;
import io.scalecube.config.jmx.JmxConfigRegistry;
import io.scalecube.config.source.ConfigSource;
import io.scalecube.config.source.ConfigSourceInfo;
import io.scalecube.config.source.LoadedConfigProperty;
import io.scalecube.config.utils.ThrowableUtil;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.management.MBeanServer;
import javax.management.ObjectName;

final class ConfigRegistryImpl implements ConfigRegistry {

  private static final Logger LOGGER = System.getLogger(ConfigRegistryImpl.class.getName());

  static final Function STRING_PARSER = str -> str;
  static final Function DOUBLE_PARSER = Double::parseDouble;
  static final Function LONG_PARSER = Long::parseLong;
  static final Function BOOLEAN_PARSER = Boolean::parseBoolean;
  static final Function INT_PARSER = Integer::parseInt;
  static final Function DURATION_PARSER = DurationParser::parseDuration;

  // reload executor

  private static final ScheduledExecutorService reloadExecutor;

  static {
    ThreadFactory threadFactory =
        r -> {
          Thread thread = new Thread(r);
          thread.setDaemon(true);
          thread.setName("config-registry");
          thread.setUncaughtExceptionHandler(
              (t, e) -> LOGGER.log(Level.ERROR, "Exception occurred", e));
          return thread;
        };
    reloadExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
  }

  // state fields

  private final ConfigRegistrySettings settings;

  private final Map configSourceStatusMap = new HashMap<>();

  private volatile Map propertyMap; // being reset on reload

  @SuppressWarnings("rawtypes")
  private final Map> propertyCallbackMap =
      new ConcurrentHashMap<>();

  private final LinkedHashMap recentConfigEvents =
      new LinkedHashMap() {
        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
          return size() > settings.getRecentConfigEventsNum();
        }
      };

  ConfigRegistryImpl(ConfigRegistrySettings settings) {
    Objects.requireNonNull(settings, "ConfigRegistrySettings can't be null");
    this.settings = settings;
  }

  void init() {
    loadAndNotify();

    if (settings.isReloadEnabled()) {
      reloadExecutor.scheduleAtFixedRate(
          () -> {
            try {
              loadAndNotify();
            } catch (Exception e) {
              LOGGER.log(Level.ERROR, "[loadAndNotify] Exception occurred, cause: {0}", e);
            }
          },
          settings.getReloadIntervalSec(),
          settings.getReloadIntervalSec(),
          TimeUnit.SECONDS);
    }

    if (settings.isJmxEnabled()) {
      registerJmxMBean();
    }
  }

  private void registerJmxMBean() {
    try {
      MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
      ObjectName objectName = new ObjectName(settings.getJmxMBeanName());
      mbeanServer.registerMBean(new JmxConfigRegistry(this), objectName);
    } catch (Exception e) {
      throw ThrowableUtil.propagate(e);
    }
  }

  @Override
  public  ObjectConfigProperty objectProperty(String name, Function mapper) {
    return new MappedObjectConfigProperty<>(
        new StringConfigPropertyImpl(name, propertyMap, propertyCallbackMap), mapper);
  }

  @Override
  public  ObjectConfigProperty objectProperty(String prefix, Class cfgClass) {
    return objectProperty(
        Arrays.stream(cfgClass.getDeclaredFields())
            .collect(Collectors.toMap(Field::getName, field -> prefix + '.' + field.getName())),
        cfgClass);
  }

  @Override
  public  ObjectConfigProperty objectProperty(
      Map bindingMap, Class cfgClass) {
    return new ObjectConfigPropertyImpl<>(bindingMap, cfgClass, propertyMap, propertyCallbackMap);
  }

  @Override
  public  ObjectConfigProperty objectProperty(Class cfgClass) {
    return objectProperty(cfgClass.getPackage().getName(), cfgClass);
  }

  @Override
  public  T objectValue(String prefix, Class cfgClass, T defaultValue) {
    return objectProperty(prefix, cfgClass).value(defaultValue);
  }

  @Override
  public  T objectValue(Map bindingMap, Class cfgClass, T defaultValue) {
    return objectProperty(bindingMap, cfgClass).value(defaultValue);
  }

  @Override
  public  T objectValue(Class cfgClass, T defaultValue) {
    return objectValue(cfgClass.getPackage().getName(), cfgClass, defaultValue);
  }

  @Override
  public StringConfigProperty stringProperty(String name) {
    return new StringConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public String stringValue(String name, String defaultValue) {
    return stringProperty(name).value(defaultValue);
  }

  @Override
  public DoubleConfigProperty doubleProperty(String name) {
    return new DoubleConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public double doubleValue(String name, double defaultValue) {
    return doubleProperty(name).value(defaultValue);
  }

  @Override
  public LongConfigProperty longProperty(String name) {
    return new LongConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public long longValue(String name, long defaultValue) {
    return longProperty(name).value(defaultValue);
  }

  @Override
  public BooleanConfigProperty booleanProperty(String name) {
    return new BooleanConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public boolean booleanValue(String name, boolean defaultValue) {
    return booleanProperty(name).value(defaultValue);
  }

  @Override
  public IntConfigProperty intProperty(String name) {
    return new IntConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public int intValue(String name, int defaultValue) {
    return intProperty(name).value(defaultValue);
  }

  @Override
  public DurationConfigProperty durationProperty(String name) {
    return new DurationConfigPropertyImpl(name, propertyMap, propertyCallbackMap);
  }

  @Override
  public Duration durationValue(String name, Duration defaultValue) {
    return durationProperty(name).value(defaultValue);
  }

  @Override
  public ListConfigProperty stringListProperty(String name) {
    return new ListConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, STRING_PARSER);
  }

  @Override
  public List stringListValue(String name, List defaultValue) {
    return stringListProperty(name).value(defaultValue);
  }

  @Override
  public ListConfigProperty doubleListProperty(String name) {
    return new ListConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, DOUBLE_PARSER);
  }

  @Override
  public List doubleListValue(String name, List defaultValue) {
    return doubleListProperty(name).value(defaultValue);
  }

  @Override
  public ListConfigProperty longListProperty(String name) {
    return new ListConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, LONG_PARSER);
  }

  @Override
  public List longListValue(String name, List defaultValue) {
    return longListProperty(name).value(defaultValue);
  }

  @Override
  public ListConfigProperty intListProperty(String name) {
    return new ListConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, INT_PARSER);
  }

  @Override
  public List intListValue(String name, List defaultValue) {
    return intListProperty(name).value(defaultValue);
  }

  @Override
  public ListConfigProperty durationListProperty(String name) {
    return new ListConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, DURATION_PARSER);
  }

  @Override
  public List durationListValue(String name, List defaultValue) {
    return durationListProperty(name).value(defaultValue);
  }

  @Override
  public MultimapConfigProperty stringMultimapProperty(String name) {
    return new MultimapConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, STRING_PARSER);
  }

  @Override
  public Map> stringMultimapValue(
      String name, Map> defaultValue) {
    return stringMultimapProperty(name).value(defaultValue);
  }

  @Override
  public MultimapConfigProperty doubleMultimapProperty(String name) {
    return new MultimapConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, DOUBLE_PARSER);
  }

  @Override
  public Map> doubleMultimapValue(
      String name, Map> defaultValue) {
    return doubleMultimapProperty(name).value(defaultValue);
  }

  @Override
  public MultimapConfigProperty longMultimapProperty(String name) {
    return new MultimapConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, LONG_PARSER);
  }

  @Override
  public Map> longMultimapValue(
      String name, Map> defaultValue) {
    return longMultimapProperty(name).value(defaultValue);
  }

  @Override
  public MultimapConfigProperty intMultimapProperty(String name) {
    return new MultimapConfigPropertyImpl<>(name, propertyMap, propertyCallbackMap, INT_PARSER);
  }

  @Override
  public Map> intMultimapValue(
      String name, Map> defaultValue) {
    return intMultimapProperty(name).value(defaultValue);
  }

  @Override
  public MultimapConfigProperty durationMultimapProperty(String name) {
    return new MultimapConfigPropertyImpl<>(
        name, propertyMap, propertyCallbackMap, DURATION_PARSER);
  }

  @Override
  public Map> durationMultimapValue(
      String name, Map> defaultValue) {
    return durationMultimapProperty(name).value(defaultValue);
  }

  @Override
  public Set allProperties() {
    return propertyMap.values().stream()
        .map(LoadedConfigProperty::name)
        .collect(Collectors.toSet());
  }

  @Override
  public Collection getConfigProperties() {
    return propertyMap.values().stream()
        .map(
            property -> {
              ConfigPropertyInfo info = new ConfigPropertyInfo();
              info.setName(property.name());
              info.setValue(property.valueAsString().orElse(null));
              info.setSource(property.source().orElse(null));
              info.setOrigin(property.origin().orElse(null));
              info.setHost(settings.getHost());
              return info;
            })
        .collect(Collectors.toList());
  }

  @Override
  public Collection getConfigSources() {
    Collection result = new ArrayList<>();
    int order = 0;
    for (Map.Entry entry : settings.getSources().entrySet()) {
      int priorityOrder = order++;
      String sourceName = entry.getKey();
      ConfigSource configSource = entry.getValue();

      ConfigSourceInfo info = new ConfigSourceInfo();
      info.setSourceName(sourceName);
      info.setPriorityOrder(priorityOrder);
      info.setConfigSourceString(configSource.toString());

      Integer status = configSourceStatusMap.get(sourceName);
      info.setHealthString(
          Optional.ofNullable(status).map(i -> i == 1 ? "Error" : "Ok").orElse("Unknown"));

      info.setHost(settings.getHost());
      result.add(info);
    }
    return result;
  }

  @Override
  public Collection getRecentConfigEvents() {
    return new LinkedHashSet<>(recentConfigEvents.keySet());
  }

  @Override
  public ConfigRegistrySettings getSettings() {
    return settings;
  }

  private void loadAndNotify() {
    // calculate new load map
    Map loadedPropertyMap = new ConcurrentHashMap<>();

    // load config from sources
    Map sources = settings.getSources();
    for (String sourceName : sources.keySet()) {
      ConfigSource source = sources.get(sourceName);

      final Map configMap;
      Throwable error = null;
      try {
        configMap = source.loadConfig();
      } catch (Exception e) {
        error = e;
        throw ThrowableUtil.propagate(e);
      } finally {
        computeConfigLoadStatus(sourceName, error);
      }

      // populate loaded properties with new field 'source'
      configMap.forEach(
          (key, configProperty) ->
              loadedPropertyMap.putIfAbsent(
                  key,
                  LoadedConfigProperty.withCopyFrom(configProperty).source(sourceName).build()));
    }

    List detectedChanges = new ArrayList<>();

    if (propertyMap == null) {
      for (String propName : loadedPropertyMap.keySet()) {
        ConfigProperty newProp = loadedPropertyMap.get(propName); // not null
        // collect changes
        detectedChanges.add(ConfigEvent.createAdded(propName, settings.getHost(), newProp));
      }
    } else {
      // Check property updates
      Set keySet1 = propertyMap.keySet();
      Set keySet2 = loadedPropertyMap.keySet();

      Set updatedProps =
          Stream.concat(keySet1.stream(), keySet2.stream())
              .filter(keySet1::contains)
              .filter(keySet2::contains)
              .collect(Collectors.toSet());

      for (String propName : updatedProps) {
        ConfigProperty newProp = loadedPropertyMap.get(propName); // not null
        ConfigProperty oldProp = propertyMap.get(propName); // not null
        // collect changes
        detectedChanges.add(
            ConfigEvent.createUpdated(propName, settings.getHost(), oldProp, newProp));
      }

      // Checks for removals
      Set removedProps =
          keySet1.stream().filter(o -> !keySet2.contains(o)).collect(Collectors.toSet());
      for (String propName : removedProps) {
        ConfigProperty oldProp = propertyMap.get(propName);
        if (oldProp != null) {
          // collect changes
          detectedChanges.add(ConfigEvent.createRemoved(propName, settings.getHost(), oldProp));
        }
      }

      // Check for new properties
      Set addedProps =
          keySet2.stream().filter(o -> !keySet1.contains(o)).collect(Collectors.toSet());
      for (String propName : addedProps) {
        ConfigProperty newProp = loadedPropertyMap.get(propName); // not null
        // collect changes
        detectedChanges.add(ConfigEvent.createAdded(propName, settings.getHost(), newProp));
      }
    }

    // reset loaded
    propertyMap = loadedPropertyMap;

    detectedChanges.forEach(input -> recentConfigEvents.put(input, null)); // keep recent changes

    reportChanges(
        detectedChanges.stream().filter(ConfigEvent::isChanged).collect(Collectors.toList()));

    // re-compute values and invoke callbacks
    detectedChanges.stream()
        .filter(event -> propertyCallbackMap.containsKey(event.getName()))
        .flatMap(
            event ->
                propertyCallbackMap.get(event.getName()).values().stream()
                    .map(callback -> new SimpleImmutableEntry<>(callback, event)))
        .collect(
            Collectors.groupingBy(
                SimpleImmutableEntry::getKey,
                Collectors.mapping(SimpleImmutableEntry::getValue, Collectors.toList())))
        .forEach(PropertyCallback::computeValue);
  }

  private void reportChanges(Collection events) {
    Collection configEvents = Collections.unmodifiableCollection(events);
    settings
        .getListeners()
        .forEach(
            (key, eventListener) -> {
              try {
                eventListener.onEvents(configEvents);
              } catch (Exception e) {
                LOGGER.log(
                    Level.ERROR,
                    "Exception on configEventListener: {0}, events: {1}",
                    key,
                    configEvents,
                    e);
              }
            });
  }

  private void computeConfigLoadStatus(String sourceName, Throwable ex) {
    int status = ex != null ? 1 : 0;
    Integer status0 = configSourceStatusMap.put(sourceName, status);
    if (status0 == null || (status0 ^ status) == 1) {
      if (status == 1) {
        LOGGER.log(Level.ERROR, "[loadConfig][{0}] Exception occurred", sourceName, ex);
      } else {
        LOGGER.log(Level.DEBUG, "[loadConfig][{0}] Loaded config properties", sourceName);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy