UpdatingConfigSupplier.java

  1. /*
  2.  * Copyright 2017 Providence Authors
  3.  *
  4.  * Licensed to the Apache Software Foundation (ASF) under one
  5.  * or more contributor license agreements. See the NOTICE file
  6.  * distributed with this work for additional information
  7.  * regarding copyright ownership. The ASF licenses this file
  8.  * to you under the Apache License, Version 2.0 (the
  9.  * "License"); you may not use this file except in compliance
  10.  * with the License. You may obtain a copy of the License at
  11.  *
  12.  *   http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing,
  15.  * software distributed under the License is distributed on an
  16.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  17.  * KIND, either express or implied. See the License for the
  18.  * specific language governing permissions and limitations
  19.  * under the License.
  20.  */
  21. package net.morimekta.providence.config.impl;

  22. import net.morimekta.providence.PMessage;
  23. import net.morimekta.providence.config.ConfigListener;
  24. import net.morimekta.providence.config.ConfigSupplier;
  25. import net.morimekta.providence.config.parser.ConfigException;
  26. import net.morimekta.providence.descriptor.PField;
  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;

  29. import javax.annotation.Nonnull;
  30. import java.lang.ref.Reference;
  31. import java.lang.ref.WeakReference;
  32. import java.time.Clock;
  33. import java.util.ArrayList;
  34. import java.util.Objects;
  35. import java.util.concurrent.atomic.AtomicLong;
  36. import java.util.concurrent.atomic.AtomicReference;

  37. /**
  38.  * A supplier and instance holder for config objects. This supplier can be
  39.  * listened to for changes in the config object. When something triggers
  40.  * a change (<code>supplier.set(config)</code>) that will cause a config
  41.  * change call to each listener regardless of if the config values actually
  42.  * did change.
  43.  */
  44. public abstract class UpdatingConfigSupplier<M extends PMessage<M>> implements ConfigSupplier<M> {
  45.     private static final Logger LOGGER = LoggerFactory.getLogger(UpdatingConfigSupplier.class);

  46.     private final AtomicReference<M>                      instance;
  47.     private final ArrayList<Reference<ConfigListener<M>>> listeners;
  48.     private final Clock                                   clock;
  49.     private final AtomicLong                              lastUpdateTimestamp;

  50.     /**
  51.      * Initialize supplier with empty config.
  52.      *
  53.      * @param clock The clock to use in timing config loads.
  54.      */
  55.     protected UpdatingConfigSupplier(@Nonnull Clock clock) {
  56.         this.instance = new AtomicReference<>();
  57.         this.listeners = new ArrayList<>();
  58.         this.clock = clock;
  59.         this.lastUpdateTimestamp = new AtomicLong(0L);
  60.     }

  61.     @Nonnull
  62.     @Override
  63.     public final M get() {
  64.         M config = instance.get();
  65.         if (config == null) {
  66.             throw new IllegalStateException("No config instance");
  67.         }
  68.         return config;
  69.     }

  70.     @Override
  71.     public <RM extends PMessage<RM>> ConfigSupplier<RM> reference(PField... fields)
  72.             throws ConfigException {
  73.         return new ReferenceConfigSupplier<>(this, clock, fields);
  74.     }

  75.     @Override
  76.     public void addListener(@Nonnull ConfigListener<M> listener) {
  77.         synchronized (this) {
  78.             listeners.removeIf(ref -> ref.get() == listener || ref.get() == null);
  79.             listeners.add(new WeakReference<>(listener));
  80.         }
  81.     }

  82.     @Override
  83.     public void removeListener(@Nonnull ConfigListener<M> listener) {
  84.         synchronized (this) {
  85.             listeners.removeIf(ref -> ref.get() == null || ref.get() == listener);
  86.         }
  87.     }

  88.     @Override
  89.     public long configTimestamp() {
  90.         return lastUpdateTimestamp.get();
  91.     }

  92.     @Override
  93.     public Clock getClock() {
  94.         return clock;
  95.     }

  96.     /**
  97.      * Set a new config value to the supplier. This is protected as it is
  98.      * usually up to the supplier implementation to enable updating the
  99.      * config at later stages.
  100.      *
  101.      * @param config The new config instance.
  102.      */
  103.     protected final void set(M config) {
  104.         ArrayList<Reference<ConfigListener<M>>> iterateOver;
  105.         synchronized (this) {
  106.             M old = instance.get();
  107.             if (Objects.equals(old, config)) {
  108.                 return;
  109.             }

  110.             instance.set(config);
  111.             lastUpdateTimestamp.set(clock.millis());
  112.             listeners.removeIf(ref -> ref.get() == null);
  113.             iterateOver = new ArrayList<>(listeners);
  114.         }
  115.         iterateOver.forEach(ref -> {
  116.             ConfigListener<M> listener = ref.get();
  117.             if (listener != null) {
  118.                 try {
  119.                     listener.onConfigChange(config);
  120.                 } catch (Exception e) {
  121.                     LOGGER.warn("Unhandled exception on config change", e);
  122.                 }
  123.             }
  124.         });
  125.     }
  126. }