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

com.yhzdys.myosotis.MyosotisApplication Maven / Gradle / Ivy

There is a newer version: 1.3.4
Show newest version
package com.yhzdys.myosotis;

import com.yhzdys.myosotis.data.CachedConfig;
import com.yhzdys.myosotis.data.ConfigMetadata;
import com.yhzdys.myosotis.entity.MyosotisConfig;
import com.yhzdys.myosotis.entity.MyosotisEvent;
import com.yhzdys.myosotis.entity.PollingData;
import com.yhzdys.myosotis.enums.EventType;
import com.yhzdys.myosotis.event.listener.ConfigListener;
import com.yhzdys.myosotis.event.listener.NamespaceListener;
import com.yhzdys.myosotis.event.multicast.EventMulticaster;
import com.yhzdys.myosotis.executor.PollingExecutor;
import com.yhzdys.myosotis.misc.LoggerFactory;
import com.yhzdys.myosotis.processor.Processor;
import com.yhzdys.myosotis.processor.ServerProcessor;
import com.yhzdys.myosotis.processor.SnapshotProcessor;
import com.yhzdys.myosotis.support.LockStore;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public final class MyosotisApplication {

    /**
     * 
     */
    private final Map clients = new ConcurrentHashMap<>(2);

    /**
     * cached config(s)
     */
    private final CachedConfig cachedConfig = new CachedConfig();

    /**
     * metadata of cached config(s)
     */
    private final ConfigMetadata configMetadata = new ConfigMetadata();

    /**
     * config(s) processor
     */
    private final Processor serverProcessor;
    private final Processor snapshotProcessor;

    private final EventMulticaster multicaster = new EventMulticaster();

    public MyosotisApplication(String serverAddress) {
        this(new Config(serverAddress));
    }

    public MyosotisApplication(Config config) {
        this.serverProcessor = new ServerProcessor(config, configMetadata);
        this.snapshotProcessor = new SnapshotProcessor(config.isEnableSnapshot());
        this.start();
    }

    public MyosotisClient getClient(String namespace) {
        MyosotisClient client = clients.get(namespace);
        if (client != null) {
            return client;
        }
        synchronized (clients) {
            client = clients.get(namespace);
            if (client != null) {
                return client;
            }
            snapshotProcessor.init(namespace);
            cachedConfig.add(namespace);
            client = new MyosotisClient(this, namespace);
            clients.put(namespace, client);
        }
        return client;
    }

    public void addNamespaceListener(NamespaceListener listener) {
        if (listener == null) {
            return;
        }
        String namespace = listener.namespace();
        if (StringUtils.isEmpty(namespace)) {
            LoggerFactory.getLogger().error("Listener's namespace may not be null");
            return;
        }
        // init client if absent
        this.getClient(namespace);
        if (configMetadata.isPollingAll(namespace)) {
            return;
        }
        synchronized (LockStore.get(namespace)) {
            if (configMetadata.isPollingAll(namespace)) {
                return;
            }
            // init namespace configs
            this.initConfigs(namespace);
            configMetadata.setPollingAll(namespace);
        }
        multicaster.addNamespaceListener(listener);
    }

    public void addConfigListener(ConfigListener listener) {
        if (listener == null) {
            return;
        }
        String namespace = listener.namespace();
        String configKey = listener.configKey();
        if (StringUtils.isEmpty(namespace)) {
            LoggerFactory.getLogger().error("Listener's namespace may not be null");
            return;
        }
        if (StringUtils.isEmpty(configKey)) {
            LoggerFactory.getLogger().error("Listener's configKey may not be null");
            return;
        }
        // init client if absent
        this.getClient(namespace);
        // init polling metadata
        String value = this.getConfig(namespace, configKey);
        if (value == null) {
            configMetadata.addPolling(namespace, configKey, 0);
        }
        multicaster.addConfigListener(listener);
    }

    public Collection clients() {
        return Collections.unmodifiableCollection(clients.values());
    }

    /**
     * get config
     * fetch order: cache > server > snapshot
     */
    String getConfig(String namespace, String configKey) {
        // step.1 get from cache
        String configValue = cachedConfig.get(namespace, configKey);
        if (configValue != null) {
            return configValue;
        }
        if (configMetadata.isAbsent(namespace, configKey)) {
            return null;
        }
        synchronized (LockStore.get(namespace + ":" + configKey)) {
            configValue = cachedConfig.get(namespace, configKey);
            if (configValue != null) {
                return configValue;
            }
            if (configMetadata.isAbsent(namespace, configKey)) {
                return null;
            }
            // step.2 get from server
            MyosotisConfig config = serverProcessor.getConfig(namespace, configKey);
            if (config != null) {
                configMetadata.addPolling(namespace, configKey, config.getVersion());
                cachedConfig.add(namespace, configKey, config.getConfigValue());
                configMetadata.removeAbsent(namespace, configKey);
                snapshotProcessor.save(config);
                return config.getConfigValue();
            }
            if (configMetadata.isAbsent(namespace, configKey)) {
                return null;
            }
            // step.3 get from snapshot
            LoggerFactory.getLogger().warn("Get config from server failed, fall-back to snapshot");
            config = snapshotProcessor.getConfig(namespace, configKey);
            if (config != null) {
                cachedConfig.add(namespace, configKey, config.getConfigValue());
                configMetadata.addPolling(namespace, configKey, config.getVersion());
                return config.getConfigValue();
            }
        }
        return null;
    }

    private void initConfigs(String namespace) {
        List configs = serverProcessor.getConfigs(namespace);
        if (CollectionUtils.isEmpty(configs)) {
            return;
        }
        for (MyosotisConfig config : configs) {
            this.initConfig(config);
        }
    }

    private void initConfig(MyosotisConfig config) {
        String namespace = config.getNamespace();
        String configKey = config.getConfigKey();

        configMetadata.removeAbsent(namespace, configKey);
        configMetadata.addPolling(namespace, configKey, config.getVersion());
        if (config.getConfigValue() != null) {
            cachedConfig.add(namespace, configKey, config.getConfigValue());
            snapshotProcessor.save(config);
        }
    }

    private void start() {
        LoggerFactory.getLogger().info("Myosotis starting...");
        final PollingExecutor executor = new PollingExecutor();
        executor.scheduleAtFixedRate(() -> {
            try {
                this.fetchEvents();
                configMetadata.clearAbsent();
            } catch (Throwable e) {
                LoggerFactory.getLogger().error("Polling config(s) failed, {}", e.getMessage());
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
            }
        }, 0, 1, TimeUnit.MILLISECONDS);
        LoggerFactory.getLogger().info("Myosotis start completed...");
    }

    private void fetchEvents() {
        Collection pollingData = configMetadata.pollingData();
        if (CollectionUtils.isEmpty(pollingData)) {
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
            return;
        }
        List events = serverProcessor.fetchEvents();
        if (CollectionUtils.isEmpty(events)) {
            return;
        }
        for (MyosotisEvent event : events) {
            if (clients.get(event.getNamespace()) != null) {
                continue;
            }
            this.processEvent(event);
        }
    }

    private void processEvent(MyosotisEvent event) {
        String namespace = event.getNamespace();
        String configKey = event.getConfigKey();
        String configValue = event.getConfigValue();

        if (EventType.UPDATE.equals(event.getType())) {
            configMetadata.addPolling(namespace, configKey, event.getVersion());
            // not really update
            if (Objects.equals(configValue, cachedConfig.get(namespace, configKey))) {
                return;
            }
            cachedConfig.add(namespace, configKey, configValue);
            snapshotProcessor.save(this.event2Config(event));
        } else {
            switch (event.getType()) {
                case ADD:
                    configMetadata.addPolling(namespace, configKey, event.getVersion());
                    cachedConfig.add(namespace, configKey, configValue);
                    snapshotProcessor.save(this.event2Config(event));
                    configMetadata.removeAbsent(namespace, configKey);
                    break;
                case DELETE:
                    if (multicaster.hasListener(namespace, configKey)) {
                        // reset polling version
                        configMetadata.addPolling(namespace, configKey, 0);
                    } else {
                        configMetadata.removePolling(namespace, configKey);
                    }
                    cachedConfig.remove(namespace, configKey);
                    configMetadata.addAbsent(namespace, configKey);
                    break;
                default:
                    return;
            }
        }
        multicaster.multicast(event);
    }

    private MyosotisConfig event2Config(MyosotisEvent event) {
        MyosotisConfig config = new MyosotisConfig();
        config.setId(event.getId());
        config.setNamespace(event.getNamespace());
        config.setConfigKey(event.getConfigKey());
        config.setConfigValue(event.getConfigValue());
        config.setVersion(event.getVersion());
        return config;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy