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

io.getunleash.polling.RefreshPolicy.kt Maven / Gradle / Ivy

The newest version!
package io.getunleash.polling

import io.getunleash.UnleashConfig
import io.getunleash.UnleashContext
import io.getunleash.cache.ToggleCache
import io.getunleash.data.Toggle
import org.slf4j.Logger
import java.io.Closeable
import java.math.BigInteger
import java.security.MessageDigest
import java9.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Used to define how to Refresh and serve toggles
 * @param unleashFetcher How to fetch toggles
 * @param cache The toggle cache
 * @param logger Allowing for logging with correct classname
 * @param config Configuring unleash
 * @param context Configuring context
 */
abstract class RefreshPolicy(
    open val unleashFetcher: UnleashFetcher,
    open val cache: ToggleCache,
    val logger: Logger,
    open val config: UnleashConfig,
    open var context: UnleashContext
) : Closeable {
    internal val listeners: MutableList = mutableListOf()
    internal val errorListeners: MutableList = mutableListOf()
    internal val checkListeners: MutableList = mutableListOf()
    internal val readyListeners: MutableList = mutableListOf()
    private var inMemoryConfig: Map = emptyMap()
    private val cacheKey: String by lazy { sha256(cacheBase.format(this.config.clientKey)) }

    companion object {
        const val cacheBase = "android_${UnleashFetcher.TOGGLE_BACKUP_NAME}_%s"
        fun sha256(s: String): String {
            val md = MessageDigest.getInstance("SHA-256")
            val digest = md.digest(s.toByteArray(Charsets.UTF_8))
            val number = BigInteger(1, digest)
            return number.toString(16).padStart(32, '0')
        }
    }

    abstract val isReady: AtomicBoolean

    fun readToggleCache(): Map {
        return try {
            this.cache.read(cacheKey)
        } catch (e: Exception) {
            inMemoryConfig
        }
    }

    fun writeToggleCache(value: Map) {
        try {
            this.inMemoryConfig = value
            this.cache.write(cacheKey, value)
            broadcastTogglesUpdated()
        } catch (e: Exception) {
        }
    }

    /**
     * Subclasses should override this to implement their way of updating the toggle cache
     */
    abstract fun getConfigurationAsync(): CompletableFuture>

    /**
     * Through this getter, child classes can use our fetcher to get the latest toggles over HTTP
     *
     * @return the fetcher
     */
    fun fetcher(): UnleashFetcher {
        return this.unleashFetcher
    }

    fun refreshAsync(): CompletableFuture {
        return this.fetcher().getTogglesAsync(context).thenAcceptAsync { response ->
            if (response.isFetched()) {
                this.writeToggleCache(response.toggles)
            }
        }
    }

    fun broadcastTogglesUpdated(): Unit {
        synchronized(listeners) {
            listeners.forEach {
                it.onTogglesUpdated()
            }
        }
    }

    fun broadcastTogglesChecked() {
        synchronized(checkListeners) {
            checkListeners.forEach {
                it.onTogglesChecked()
            }
        }
    }

    fun broadcastTogglesErrored(e: Exception) {
        synchronized(errorListeners) {
            errorListeners.forEach {
                it.onError(e)
            }
        }
    }

    fun broadcastReady() {
        synchronized(readyListeners) {
            readyListeners.forEach {
                it.onReady()
            }
        }
    }

    /**
     * Subclasses should override this to implement their way of manually starting polling after context is updated.
     * Typical usage would be to use [PollingModes.manuallyStartPolling] or [PollingModes.manuallyStartedPollMs] to create/configure your polling mode,
     * and then call [startPolling()] on the Unleash client
     */
    abstract fun startPolling()

    /**
     * Subclasses should override this to signal to Unleash whether or not they're actively polling
     */
    abstract fun isPolling(): Boolean

    fun getLatestCachedValue(): Map = this.inMemoryConfig

    override fun close() {
        this.unleashFetcher.close()
    }

    fun addTogglesUpdatedListener(listener: TogglesUpdatedListener): Unit {
        synchronized(listeners) {
            listeners.add(listener)
        }
    }

    fun removeTogglesUpdatedListener(listener: TogglesUpdatedListener) {
        synchronized(listeners) {
            listeners.remove(listener)
        }
    }

    fun addTogglesErroredListener(errorListener: TogglesErroredListener): Unit {
        synchronized(errorListeners) {
            errorListeners.add(errorListener)
        }
    }

    fun removeTogglesErroredListener(listener: TogglesErroredListener): Unit {
        synchronized(errorListeners) {
            errorListeners.remove(listener)
        }
    }

    fun addTogglesCheckedListener(checkListener: TogglesCheckedListener) {
        synchronized(checkListeners) {
            checkListeners.add(checkListener)
        }
    }

    fun addReadyListener(readyListener: ReadyListener) {
        synchronized(readyListeners) {
            readyListeners.add(readyListener)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy